Version Description
(Date: June 01, 2019) = * Added lots of UI improvement * Add option to add custom css and JS * add response filter options * add option to export data * Add Dashboard widget for quick look * Fixes for some form fields
Download this release
Release Info
Developer | techjewel |
Plugin | Contact Form Plugin – Fastest Contact Form Builder Plugin for WordPress by Fluent Forms |
Version | 2.0.0 |
Comparing to | |
See all releases |
Code changes from version 1.8.0 to 2.0.0
- app/Global/Common.php +14 -0
- app/Helpers/Str.php +1 -1
- app/Hooks/Ajax.php +12 -0
- app/Hooks/Backend.php +16 -9
- app/Hooks/Common.php +8 -0
- app/Modules/Activator.php +27 -6
- app/Modules/Component/BaseComponent.php +209 -0
- app/Modules/Component/Component.php +658 -439
- app/Modules/Component/ComponentInitTrait.php +253 -0
- app/Modules/DashboardWidgetModule.php +114 -0
- app/Modules/DocumentationModule.php +12 -12
- app/Modules/Entries/Entries.php +26 -2
- app/Modules/Entries/EntryQuery.php +25 -10
- app/Modules/Entries/Export.php +126 -19
- app/Modules/Form/Form.php +176 -124
- app/Modules/Form/FormHandler.php +3 -1
- app/Modules/Form/Predefined.php +134 -10
- app/Modules/Form/Settings/FormCssJs.php +119 -0
- app/Modules/Form/Settings/Validator/Confirmations.php +4 -4
- app/Modules/Integration/ActiveCampaign/Integrator.php +0 -129
- app/Modules/Integration/BaseIntegration.php +0 -103
- app/Modules/Integration/MailChimpIntegration.php +0 -172
- app/Modules/Integration/MailChimpSubscriber.php +0 -97
- app/Modules/Registerer/Menu.php +29 -7
- app/Providers/ActiveCampaignApiProvider.php +0 -13
- app/Providers/BackgroundProcessingProvider.php +0 -11
- app/Providers/CsvProvider.php +0 -13
- app/Services/BackgroundProcessing/classes/wp-async-request.php +0 -163
- app/Services/BackgroundProcessing/classes/wp-background-process.php +0 -506
- app/Services/BackgroundProcessing/wp-background-processing.php +0 -20
- app/Services/FormBuilder/Components/DateTime.php +80 -24
- app/Services/FormBuilder/Components/Select.php +3 -0
- app/Services/FormBuilder/DefaultElements.php +122 -122
- app/Services/FormBuilder/EditorShortCode.php +68 -65
- app/Services/FormBuilder/EditorShortcodeParser.php +29 -4
- app/Services/FormBuilder/ElementCustomization.php +117 -158
- app/Services/FormBuilder/ElementSettingsPlacement.php +9 -9
- app/Services/FormBuilder/FormBuilder.php +3 -2
- app/Services/FormBuilder/MessageShortCodeParser.php +0 -192
- app/Services/FormBuilder/Notifications/AsyncEmailSender.php +0 -45
- app/Services/FormBuilder/Notifications/EmailNotification.php +25 -1
- app/Services/FormBuilder/Notifications/EmailNotificationActions.php +3 -2
- app/Services/FormBuilder/ShortCodeParser.php +18 -1
- app/Services/FormBuilder/ValidationRuleSettings.php +39 -39
- app/Services/Integrations/MailChimp.php +0 -441
- app/Services/Integrations/MailChimp/MailChimpAsyncSubscriber.php +0 -37
- app/Services/Integrations/Slack/SlackAsyncNotifier.php +0 -37
- app/Services/Integrations/activecampaign-api-php/.gitignore +0 -4
- app/Services/Integrations/activecampaign-api-php/LICENSE +0 -21
- app/Services/Integrations/activecampaign-api-php/README.md +0 -83
- app/Services/Integrations/activecampaign-api-php/composer.json +0 -24
- app/Services/Integrations/activecampaign-api-php/examples-composer/composer.json +0 -15
- app/Services/Integrations/activecampaign-api-php/examples-composer/index.php +0 -149
- app/Services/Integrations/activecampaign-api-php/examples.php +0 -165
- app/Services/Integrations/activecampaign-api-php/includes/Account.class.php +0 -73
- app/Services/Integrations/activecampaign-api-php/includes/ActiveCampaign.class.php +0 -192
- app/Services/Integrations/activecampaign-api-php/includes/Auth.class.php +0 -25
- app/Services/Integrations/activecampaign-api-php/includes/Automation.class.php +0 -51
- app/Services/Integrations/activecampaign-api-php/includes/Campaign.class.php +0 -133
- app/Services/Integrations/activecampaign-api-php/includes/Connector.class.php +0 -361
- app/Services/Integrations/activecampaign-api-php/includes/Contact.class.php +0 -127
- app/Services/Integrations/activecampaign-api-php/includes/Deal.class.php +0 -139
- app/Services/Integrations/activecampaign-api-php/includes/Design.class.php +0 -31
- app/Services/Integrations/activecampaign-api-php/includes/Form.class.php +0 -298
- app/Services/Integrations/activecampaign-api-php/includes/Group.class.php +0 -55
- app/Services/Integrations/activecampaign-api-php/includes/List.class.php +0 -91
- app/Services/Integrations/activecampaign-api-php/includes/Message.class.php +0 -103
- app/Services/Integrations/activecampaign-api-php/includes/Organization.class.php +0 -25
- app/Services/Integrations/activecampaign-api-php/includes/Segment.class.php +0 -26
- app/Services/Integrations/activecampaign-api-php/includes/Settings.class.php +0 -25
- app/Services/Integrations/activecampaign-api-php/includes/Subscriber.class.php +0 -6
- app/Services/Integrations/activecampaign-api-php/includes/Tag.class.php +0 -25
- app/Services/Integrations/activecampaign-api-php/includes/Tracking.class.php +0 -118
- app/Services/Integrations/activecampaign-api-php/includes/User.class.php +0 -71
- app/Services/Integrations/activecampaign-api-php/includes/Webhook.class.php +0 -84
- app/Services/Integrations/activecampaign-api-php/includes/config.php +0 -6
- app/Services/Integrations/activecampaign-api-php/includes/exceptions/RequestException.php +0 -25
- app/Services/Slack.php +0 -119
- app/Services/Spout/Autoloader/Psr4Autoloader.php +150 -0
- app/Services/Spout/Autoloader/autoload.php +15 -0
- app/Services/Spout/Common/Escaper/CSV.php +38 -0
- app/Services/Spout/Common/Escaper/EscaperInterface.php +27 -0
- app/Services/Spout/Common/Escaper/ODS.php +66 -0
- app/Services/Spout/Common/Escaper/XLSX.php +185 -0
- app/Services/Spout/Common/Exception/EncodingConversionException.php +13 -0
- app/Services/Spout/Common/Exception/IOException.php +13 -0
- app/Services/Spout/Common/Exception/InvalidArgumentException.php +13 -0
- app/Services/Spout/Common/Exception/SpoutException.php +13 -0
- app/Services/Spout/Common/Exception/UnsupportedTypeException.php +13 -0
- app/Services/Spout/Common/Helper/EncodingHelper.php +175 -0
- app/Services/Spout/Common/Helper/FileSystemHelper.php +133 -0
- app/Services/Spout/Common/Helper/GlobalFunctionsHelper.php +318 -0
- app/Services/Spout/Common/Helper/StringHelper.php +71 -0
- app/Services/Spout/Common/Singleton.php +41 -0
- app/Services/Spout/Common/Type.php +16 -0
- app/Services/Spout/Reader/AbstractReader.php +238 -0
- app/Services/Spout/Reader/CSV/Reader.php +149 -0
- app/Services/Spout/Reader/CSV/ReaderOptions.php +110 -0
- app/Services/Spout/Reader/CSV/RowIterator.php +260 -0
- app/Services/Spout/Reader/CSV/Sheet.php +62 -0
- app/Services/Spout/Reader/CSV/SheetIterator.php +95 -0
- app/Services/Spout/Reader/Common/ReaderOptions.php +58 -0
- app/Services/Spout/Reader/Common/XMLProcessor.php +152 -0
- app/Services/Spout/Reader/Exception/IteratorNotRewindableException.php +13 -0
- app/Services/Spout/Reader/Exception/NoSheetsFoundException.php +13 -0
- app/Services/Spout/Reader/Exception/ReaderException.php +15 -0
- app/Services/Spout/Reader/Exception/ReaderNotOpenedException.php +13 -0
- app/Services/Spout/Reader/Exception/SharedStringNotFoundException.php +13 -0
- app/Services/Spout/Reader/Exception/XMLProcessingException.php +12 -0
- app/Services/Spout/Reader/IteratorInterface.php +18 -0
- app/Services/Spout/Reader/ODS/Helper/CellValueFormatter.php +231 -0
- app/Services/Spout/Reader/ODS/Helper/SettingsHelper.php +51 -0
- app/Services/Spout/Reader/ODS/Reader.php +85 -0
- app/Services/Spout/Reader/ODS/ReaderOptions.php +14 -0
- app/Services/Spout/Reader/ODS/RowIterator.php +352 -0
- app/Services/Spout/Reader/ODS/Sheet.php +81 -0
- app/Services/Spout/Reader/ODS/SheetIterator.php +168 -0
- app/Services/Spout/Reader/ReaderFactory.php +48 -0
- app/Services/Spout/Reader/ReaderInterface.php +36 -0
- app/Services/Spout/Reader/SheetInterface.php +18 -0
- app/Services/Spout/Reader/Wrapper/XMLInternalErrorsHelper.php +82 -0
- app/Services/Spout/Reader/Wrapper/XMLReader.php +175 -0
- app/Services/Spout/Reader/XLSX/Helper/CellHelper.php +106 -0
- app/Services/Spout/Reader/XLSX/Helper/CellValueFormatter.php +300 -0
- app/Services/Spout/Reader/XLSX/Helper/DateFormatHelper.php +124 -0
- app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/CachingStrategyFactory.php +159 -0
- app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/CachingStrategyInterface.php +44 -0
- app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/FileBasedStrategy.php +193 -0
- app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/InMemoryStrategy.php +83 -0
- app/Services/Spout/Reader/XLSX/Helper/SharedStringsHelper.php +234 -0
- app/Services/Spout/Reader/XLSX/Helper/SheetHelper.php +156 -0
- app/Services/Spout/Reader/XLSX/Helper/StyleHelper.php +330 -0
- app/Services/Spout/Reader/XLSX/Reader.php +113 -0
- app/Services/Spout/Reader/XLSX/ReaderOptions.php +33 -0
- app/Services/Spout/Reader/XLSX/RowIterator.php +405 -0
- app/Services/Spout/Reader/XLSX/Sheet.php +79 -0
- app/Services/Spout/Reader/XLSX/SheetIterator.php +114 -0
- app/Services/Spout/Writer/AbstractMultiSheetsWriter.php +119 -0
- app/Services/Spout/Writer/AbstractWriter.php +384 -0
- app/Services/Spout/Writer/CSV/Writer.php +118 -0
- app/Services/Spout/Writer/Common/Helper/AbstractStyleHelper.php +138 -0
- app/Services/Spout/Writer/Common/Helper/CellHelper.php +91 -0
- app/Services/Spout/Writer/Common/Helper/ZipHelper.php +217 -0
- app/Services/Spout/Writer/Common/Internal/AbstractWorkbook.php +192 -0
- app/Services/Spout/Writer/Common/Internal/WorkbookInterface.php +74 -0
- app/Services/Spout/Writer/Common/Internal/WorksheetInterface.php +40 -0
- app/Services/Spout/Writer/Common/Sheet.php +183 -0
- app/Services/Spout/Writer/Exception/Border/InvalidNameException.php +16 -0
- app/Services/Spout/Writer/Exception/Border/InvalidStyleException.php +16 -0
- app/Services/Spout/Writer/Exception/Border/InvalidWidthException.php +16 -0
- app/Services/Spout/Writer/Exception/InvalidColorException.php +13 -0
- app/Services/Spout/Writer/Exception/InvalidSheetNameException.php +13 -0
- app/Services/Spout/Writer/Exception/SheetNotFoundException.php +13 -0
- app/Services/Spout/Writer/Exception/WriterAlreadyOpenedException.php +13 -0
- app/Services/Spout/Writer/Exception/WriterException.php +15 -0
- app/Services/Spout/Writer/Exception/WriterNotOpenedException.php +13 -0
- app/Services/Spout/Writer/ODS/Helper/BorderHelper.php +68 -0
- app/Services/Spout/Writer/ODS/Helper/FileSystemHelper.php +279 -0
- app/Services/Spout/Writer/ODS/Helper/StyleHelper.php +356 -0
- app/Services/Spout/Writer/ODS/Internal/Workbook.php +119 -0
- app/Services/Spout/Writer/ODS/Internal/Worksheet.php +233 -0
- app/Services/Spout/Writer/ODS/Writer.php +93 -0
- app/Services/Spout/Writer/Style/Border.php +85 -0
- app/Services/Spout/Writer/Style/BorderBuilder.php +75 -0
- app/Services/Spout/Writer/Style/BorderPart.php +184 -0
- app/Services/Spout/Writer/Style/Color.php +87 -0
- app/Services/Spout/Writer/Style/Style.php +426 -0
- app/Services/Spout/Writer/Style/StyleBuilder.php +159 -0
- app/Services/Spout/Writer/WriterFactory.php +48 -0
- app/Services/Spout/Writer/WriterInterface.php +91 -0
- app/Services/Spout/Writer/XLSX/Helper/BorderHelper.php +68 -0
- app/Services/Spout/Writer/XLSX/Helper/FileSystemHelper.php +371 -0
- app/Services/Spout/Writer/XLSX/Helper/SharedStringsHelper.php +107 -0
- app/Services/Spout/Writer/XLSX/Helper/StyleHelper.php +344 -0
- app/Services/Spout/Writer/XLSX/Internal/Workbook.php +135 -0
- app/Services/Spout/Writer/XLSX/Internal/Worksheet.php +275 -0
- app/Services/Spout/Writer/XLSX/Writer.php +132 -0
- app/Services/csv/LICENSE +0 -20
- app/Services/csv/autoload.php +0 -23
- app/Services/csv/composer.json +0 -47
- app/Services/csv/src/AbstractCsv.php +0 -322
- app/Services/csv/src/Config/Controls.php +0 -288
- app/Services/csv/src/Config/Output.php +0 -282
- app/Services/csv/src/Exception/InvalidRowException.php +0 -69
- app/Services/csv/src/Modifier/MapIterator.php +0 -56
- app/Services/csv/src/Modifier/QueryFilter.php +0 -352
- app/Services/csv/src/Modifier/RowFilter.php +0 -179
- app/Services/csv/src/Modifier/StreamFilter.php +0 -294
- app/Services/csv/src/Plugin/ColumnConsistencyValidator.php +0 -98
- app/Services/csv/src/Plugin/ForbiddenNullValuesValidator.php +0 -39
- app/Services/csv/src/Plugin/SkipNullValuesFormatter.php +0 -37
- app/Services/csv/src/Reader.php +0 -279
- app/Services/csv/src/Writer.php +0 -164
- app/Services/fluentvalidator/fluentvalidator.php +3 -3
- app/Services/fluentvalidator/src/Contracts/File.php +34 -0
- app/Services/fluentvalidator/src/ValidatesAttributes.php +1 -1
- app/Services/fluentvalidator/src/ValidationData.php +1 -1
- app/Services/fluentvalidator/src/ValidationRuleParser.php +1 -1
- config/app.php +2 -2
- fluentform.php +4 -4
- framework/Foundation/AppProvider.php +6 -7
- framework/Foundation/Application.php +26 -19
- framework/Foundation/Bootstrap.php +16 -34
- framework/Request/File.php +7 -11
- framework/Request/RequestProvider.php +3 -5
- glue.json +1 -1
- public/css/add-ons.css +1 -1
- public/css/admin_docs.css +1 -1
- public/css/fluent-all-forms.css +1 -1
app/Global/Common.php
CHANGED
@@ -6,6 +6,7 @@
|
|
6 |
*/
|
7 |
|
8 |
use FluentForm\App\Services\FormBuilder\EditorShortCode;
|
|
|
9 |
|
10 |
if (!function_exists('wpFluentForm')) {
|
11 |
function wpFluentForm($key = null) {
|
@@ -13,6 +14,12 @@ if (!function_exists('wpFluentForm')) {
|
|
13 |
}
|
14 |
}
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
if (! function_exists('dd')) {
|
17 |
function dd()
|
18 |
{
|
@@ -184,3 +191,10 @@ if (!function_exists('fluentFormIsHandlingSubmission')) {
|
|
184 |
return fluentFormWasSubmitted() || isWpAsyncRequest('fluentform_async_request');
|
185 |
}
|
186 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
*/
|
7 |
|
8 |
use FluentForm\App\Services\FormBuilder\EditorShortCode;
|
9 |
+
use FluentForm\App\Modules\Component\BaseComponent as FluentFormComponent;
|
10 |
|
11 |
if (!function_exists('wpFluentForm')) {
|
12 |
function wpFluentForm($key = null) {
|
14 |
}
|
15 |
}
|
16 |
|
17 |
+
if (!function_exists('wpFluentFormAddComponent')) {
|
18 |
+
function wpFluentFormAddComponent(FluentFormComponent $component) {
|
19 |
+
return $component->_init();
|
20 |
+
}
|
21 |
+
}
|
22 |
+
|
23 |
if (! function_exists('dd')) {
|
24 |
function dd()
|
25 |
{
|
191 |
return fluentFormWasSubmitted() || isWpAsyncRequest('fluentform_async_request');
|
192 |
}
|
193 |
}
|
194 |
+
|
195 |
+
function fluentform_mb_strpos($haystack, $needle) {
|
196 |
+
if(function_exists('mb_strpos')) {
|
197 |
+
return mb_strpos($haystack, $needle);
|
198 |
+
}
|
199 |
+
return strpos($haystack, $needle);
|
200 |
+
}
|
app/Helpers/Str.php
CHANGED
@@ -50,7 +50,7 @@ class Str
|
|
50 |
public static function contains($haystack, $needles)
|
51 |
{
|
52 |
foreach ((array) $needles as $needle) {
|
53 |
-
if ($needle != '' &&
|
54 |
return true;
|
55 |
}
|
56 |
}
|
50 |
public static function contains($haystack, $needles)
|
51 |
{
|
52 |
foreach ((array) $needles as $needle) {
|
53 |
+
if ($needle != '' && fluentform_mb_strpos($haystack, $needle) !== false) {
|
54 |
return true;
|
55 |
}
|
56 |
}
|
app/Hooks/Ajax.php
CHANGED
@@ -54,6 +54,10 @@ $app->addAdminAjaxAction('fluentform-form-delete', function () use ($app) {
|
|
54 |
(new \FluentForm\App\Modules\Form\Form($app))->delete();
|
55 |
});
|
56 |
|
|
|
|
|
|
|
|
|
57 |
$app->addAdminAjaxAction('fluentform-form-inputs', function () use ($app) {
|
58 |
(new \FluentForm\App\Modules\Form\Inputs($app))->index();
|
59 |
});
|
@@ -78,6 +82,14 @@ $app->addAdminAjaxAction('fluentform-settings-formSettings-remove', function ()
|
|
78 |
(new \FluentForm\App\Modules\Form\Settings\FormSettings($app))->remove();
|
79 |
});
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
$app->addAdminAjaxAction('fluentform-load-editor-components', function () use ($app) {
|
82 |
(new \FluentForm\App\Modules\Component\Component($app))->index();
|
83 |
});
|
54 |
(new \FluentForm\App\Modules\Form\Form($app))->delete();
|
55 |
});
|
56 |
|
57 |
+
$app->addAdminAjaxAction('fluentform-form-duplicate', function () use ($app) {
|
58 |
+
(new \FluentForm\App\Modules\Form\Form($app))->duplicate();
|
59 |
+
});
|
60 |
+
|
61 |
$app->addAdminAjaxAction('fluentform-form-inputs', function () use ($app) {
|
62 |
(new \FluentForm\App\Modules\Form\Inputs($app))->index();
|
63 |
});
|
82 |
(new \FluentForm\App\Modules\Form\Settings\FormSettings($app))->remove();
|
83 |
});
|
84 |
|
85 |
+
$app->addAdminAjaxAction('fluentform-get-form-custom_css_js', function () {
|
86 |
+
(new \FluentForm\App\Modules\Form\Settings\FormCssJs)->getSettingsAjax();
|
87 |
+
});
|
88 |
+
|
89 |
+
$app->addAdminAjaxAction('fluentform-save-form-custom_css_js', function () {
|
90 |
+
(new \FluentForm\App\Modules\Form\Settings\FormCssJs)->saveSettingsAjax();
|
91 |
+
});
|
92 |
+
|
93 |
$app->addAdminAjaxAction('fluentform-load-editor-components', function () use ($app) {
|
94 |
(new \FluentForm\App\Modules\Component\Component($app))->index();
|
95 |
});
|
app/Hooks/Backend.php
CHANGED
@@ -18,15 +18,6 @@ $app->addAction('media_buttons', function () {
|
|
18 |
(new \FluentForm\App\Modules\EditorButtonModule())->addButton();
|
19 |
});
|
20 |
|
21 |
-
//$app->addAction('admin_init', function () {
|
22 |
-
// (new FluentForm\App\Modules\Track\TrackModule())->initTrack();
|
23 |
-
//});
|
24 |
-
//
|
25 |
-
//$app->addAction('admin_notices', function () {
|
26 |
-
// FluentForm\AdminNotice::showNotice();
|
27 |
-
//});
|
28 |
-
|
29 |
-
|
30 |
$app->addAction('fluentform_addons_page_render_fluentform_add_ons', function () {
|
31 |
(new \FluentForm\App\Modules\AddOnModule() )->showFluentAddOns();
|
32 |
});
|
@@ -45,3 +36,19 @@ $app->addAction('init', function () {
|
|
45 |
$roleManager = new \FluentForm\App\Modules\Acl\RoleManager();
|
46 |
$roleManager->verifyPermissionSet();
|
47 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
(new \FluentForm\App\Modules\EditorButtonModule())->addButton();
|
19 |
});
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
$app->addAction('fluentform_addons_page_render_fluentform_add_ons', function () {
|
22 |
(new \FluentForm\App\Modules\AddOnModule() )->showFluentAddOns();
|
23 |
});
|
36 |
$roleManager = new \FluentForm\App\Modules\Acl\RoleManager();
|
37 |
$roleManager->verifyPermissionSet();
|
38 |
});
|
39 |
+
|
40 |
+
$app->addAction('fluentform_global_menu', function () use ($app) {
|
41 |
+
$menu = new \FluentForm\App\Modules\Registerer\Menu($app);
|
42 |
+
$menu->renderGlobalMenu();
|
43 |
+
});
|
44 |
+
|
45 |
+
|
46 |
+
$app->addAction('wp_dashboard_setup', function () {
|
47 |
+
$roleManager = new \FluentForm\App\Modules\Acl\RoleManager();
|
48 |
+
if(!$roleManager->currentUserFormFormCapability()) {
|
49 |
+
return;
|
50 |
+
}
|
51 |
+
wp_add_dashboard_widget('fluenform_stat_widget', __('FluentForm Latest Form Submissions', 'fluentform'), function () {
|
52 |
+
(new \FluentForm\App\Modules\DashboardWidgetModule)->showStat();
|
53 |
+
}, 10, 1);
|
54 |
+
});
|
app/Hooks/Common.php
CHANGED
@@ -162,3 +162,11 @@ $app->addFilter('fluentform-disabled_analytics', function ($status) {
|
|
162 |
}
|
163 |
return $status;
|
164 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
}
|
163 |
return $status;
|
164 |
});
|
165 |
+
|
166 |
+
$app->addAction('fluentform_before_form_render', function ($form) {
|
167 |
+
(new \FluentForm\App\Modules\Form\Settings\FormCssJs)->addJs($form);
|
168 |
+
});
|
169 |
+
|
170 |
+
$app->addAction('fluentform_after_form_render', function ($form) {
|
171 |
+
(new \FluentForm\App\Modules\Form\Settings\FormCssJs)->addCss($form);
|
172 |
+
});
|
app/Modules/Activator.php
CHANGED
@@ -10,17 +10,38 @@ class Activator
|
|
10 |
* This method will be called on plugin activation
|
11 |
* @return void
|
12 |
*/
|
13 |
-
public function handleActivation()
|
14 |
{
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
-
|
|
|
|
|
|
|
|
|
19 |
Acl::setPermissions();
|
20 |
|
21 |
$this->setDefaultGlobalSettings();
|
22 |
$this->setCurrentVersion();
|
23 |
-
|
|
|
24 |
|
25 |
private function setDefaultGlobalSettings()
|
26 |
{
|
@@ -38,6 +59,6 @@ class Activator
|
|
38 |
|
39 |
private function setCurrentVersion()
|
40 |
{
|
41 |
-
update_option('_fluentform_installed_version',
|
42 |
}
|
43 |
}
|
10 |
* This method will be called on plugin activation
|
11 |
* @return void
|
12 |
*/
|
13 |
+
public function handleActivation($network_wide)
|
14 |
{
|
15 |
+
global $wpdb;
|
16 |
+
if ($network_wide) {
|
17 |
+
// Retrieve all site IDs from this network (WordPress >= 4.6 provides easy to use functions for that).
|
18 |
+
if (function_exists('get_sites') && function_exists('get_current_network_id')) {
|
19 |
+
$site_ids = get_sites(array('fields' => 'ids', 'network_id' => get_current_network_id()));
|
20 |
+
} else {
|
21 |
+
$site_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs WHERE site_id = $wpdb->siteid;");
|
22 |
+
}
|
23 |
+
// Install the plugin for all these sites.
|
24 |
+
foreach ($site_ids as $site_id) {
|
25 |
+
switch_to_blog($site_id);
|
26 |
+
$this->migrate();
|
27 |
+
restore_current_blog();
|
28 |
+
}
|
29 |
+
} else {
|
30 |
+
$this->migrate();
|
31 |
+
}
|
32 |
+
}
|
33 |
|
34 |
+
public function migrate()
|
35 |
+
{
|
36 |
+
// We are going to migrate all the database tables here.
|
37 |
+
DatabaseMigrator::run();
|
38 |
+
// Assign fluentform permission set.
|
39 |
Acl::setPermissions();
|
40 |
|
41 |
$this->setDefaultGlobalSettings();
|
42 |
$this->setCurrentVersion();
|
43 |
+
}
|
44 |
+
|
45 |
|
46 |
private function setDefaultGlobalSettings()
|
47 |
{
|
59 |
|
60 |
private function setCurrentVersion()
|
61 |
{
|
62 |
+
update_option('_fluentform_installed_version', FLUENTFORM_VERSION);
|
63 |
}
|
64 |
}
|
app/Modules/Component/BaseComponent.php
ADDED
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace FluentForm\App\Modules\Component;
|
4 |
+
|
5 |
+
abstract class BaseComponent
|
6 |
+
{
|
7 |
+
use ComponentInitTrait;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* The unique name of the component.
|
11 |
+
*
|
12 |
+
* @return string
|
13 |
+
*/
|
14 |
+
abstract public function name();
|
15 |
+
|
16 |
+
/**
|
17 |
+
* The label of the component.
|
18 |
+
*
|
19 |
+
* @return string
|
20 |
+
*/
|
21 |
+
abstract public function label();
|
22 |
+
|
23 |
+
/**
|
24 |
+
* The element type of the component from already
|
25 |
+
* available elements (input_text, textarea e.t.c).
|
26 |
+
*
|
27 |
+
* @return string
|
28 |
+
*/
|
29 |
+
abstract public function element();
|
30 |
+
|
31 |
+
/**
|
32 |
+
* The template type of the component to display preview in editor dropzone from
|
33 |
+
* already available templates (inputText, selectCountry, addressFields e.t.c).
|
34 |
+
*
|
35 |
+
* @return string
|
36 |
+
*/
|
37 |
+
abstract public function template();
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Render the element on frontend form.
|
41 |
+
*
|
42 |
+
* @param array $component Element Config
|
43 |
+
* @param object $form The Form Object
|
44 |
+
*
|
45 |
+
* @return void
|
46 |
+
*/
|
47 |
+
abstract public function render($component, $form);
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Form submission callback.
|
51 |
+
*
|
52 |
+
* @param array $formData Submitted form data
|
53 |
+
* @param int $formId Submitted form id
|
54 |
+
* @param array $config Form elements config
|
55 |
+
*
|
56 |
+
* @return array $formData
|
57 |
+
*/
|
58 |
+
abstract public function onSubmit($formData, $formId, $config);
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Component position in editor. If null is returned then
|
62 |
+
* the element will be pushed at last but the derived class
|
63 |
+
* will override this method if any customization is needed.
|
64 |
+
*
|
65 |
+
* @return int|null
|
66 |
+
*/
|
67 |
+
public function index()
|
68 |
+
{
|
69 |
+
return null;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* The group, where the component should be added. By default,
|
74 |
+
* the element will be added in "general" group and the derived
|
75 |
+
* class will override this method if any customization is needed.
|
76 |
+
*
|
77 |
+
* @return string general|advanced|container
|
78 |
+
*/
|
79 |
+
public function group()
|
80 |
+
{
|
81 |
+
return 'general';
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* The element icon class for the component to display in the button in
|
86 |
+
* the editor element list which is mapped to editor_options.icon_class.
|
87 |
+
*
|
88 |
+
* @return string
|
89 |
+
*/
|
90 |
+
public function elementIconClass()
|
91 |
+
{
|
92 |
+
return 'icon-cog';
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Element editor/form attributes and the derived class will
|
97 |
+
* override this method if any customization is needed.
|
98 |
+
*
|
99 |
+
* @param array $dafault
|
100 |
+
* @return array $default
|
101 |
+
*/
|
102 |
+
public function attributes($default)
|
103 |
+
{
|
104 |
+
return $default;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Element editor settings and the derived class will
|
109 |
+
* override this method if any customization is needed.
|
110 |
+
*
|
111 |
+
* @param array $dafault
|
112 |
+
* @return array $default
|
113 |
+
*/
|
114 |
+
public function settings($default)
|
115 |
+
{
|
116 |
+
return $default;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Element editor options and the derived class will
|
121 |
+
* override this method if any customization is needed.
|
122 |
+
*
|
123 |
+
* @param array $dafault
|
124 |
+
* @return array $default
|
125 |
+
*/
|
126 |
+
public function options($default)
|
127 |
+
{
|
128 |
+
return $default;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Element's form submission validation rules and the derived
|
133 |
+
* class will override this method if needed any customization.
|
134 |
+
*
|
135 |
+
* @return array
|
136 |
+
*/
|
137 |
+
public function validationRules()
|
138 |
+
{
|
139 |
+
return [];
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Element editor placement settings and the derived class
|
144 |
+
* will override this method if any customization is needed.
|
145 |
+
*
|
146 |
+
* @param array $placemenSettings
|
147 |
+
* @return array $placemenSettings
|
148 |
+
*/
|
149 |
+
public function placementSettings($placemenSettings)
|
150 |
+
{
|
151 |
+
return $placemenSettings;
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* The customization for the component and derived class can
|
156 |
+
* override this method if any customization is needed.
|
157 |
+
*
|
158 |
+
* @param array $customizationSettings
|
159 |
+
* @return array $customizationSettings
|
160 |
+
*/
|
161 |
+
public function customizationSettings($customizationSettings)
|
162 |
+
{
|
163 |
+
return $customizationSettings;
|
164 |
+
}
|
165 |
+
|
166 |
+
/**
|
167 |
+
* Add the component type in fluentform field types to
|
168 |
+
* be available in FormFieldParser and derived class can
|
169 |
+
* override this method if any customization is needed.
|
170 |
+
*
|
171 |
+
* @param array $types
|
172 |
+
* @return array $types
|
173 |
+
*/
|
174 |
+
public function addType($types)
|
175 |
+
{
|
176 |
+
$types[] = $this->name();
|
177 |
+
|
178 |
+
return $types;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* The keywords to search the element in the editor and
|
183 |
+
* the derived class will override this method if needed.
|
184 |
+
*
|
185 |
+
* @return array
|
186 |
+
*/
|
187 |
+
public function searchBy()
|
188 |
+
{
|
189 |
+
return [$this->name(), $this->label()];
|
190 |
+
}
|
191 |
+
|
192 |
+
/**
|
193 |
+
* Transform the element's submitted value saved in database
|
194 |
+
* to show it properly/formatted in entry page if needed and
|
195 |
+
* this method implementation optional so a default method is
|
196 |
+
* implemented here and original value is returned. In any case
|
197 |
+
* if any customization of the value is needed then the derived
|
198 |
+
* class will override it and will format and return the the value.
|
199 |
+
*
|
200 |
+
* @param mixed $value [description]
|
201 |
+
* @param string $field
|
202 |
+
* @param int $formId
|
203 |
+
* @return mixed $value
|
204 |
+
*/
|
205 |
+
public function formatEntryValue($value, $field, $formId)
|
206 |
+
{
|
207 |
+
return $value;
|
208 |
+
}
|
209 |
+
}
|
app/Modules/Component/Component.php
CHANGED
@@ -7,228 +7,359 @@ use FluentForm\Framework\Foundation\Application;
|
|
7 |
use FluentForm\App\Services\FormBuilder\EditorShortcodeParser;
|
8 |
use FluentForm\App\Services\FormBuilder\Notifications\EmailNotificationActions;
|
9 |
|
10 |
-
class Component
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
92 |
}
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
$output = $this->processOutput($output, $form);
|
180 |
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
'isDisableAnalytics' => apply_filters('fluentform-disabled_analytics', false)
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
}
|
233 |
|
234 |
/**
|
@@ -249,233 +380,321 @@ class Component {
|
|
249 |
|
250 |
foreach ($patterns as $pattern) {
|
251 |
// The default value for each pattern will be resolved here.
|
252 |
-
$attrDefaultValues[$pattern] =
|
253 |
}
|
254 |
-
|
255 |
// Raising an event so that others can hook into it and modify the default values later.
|
256 |
-
$attrDefaultValues = (array)
|
257 |
-
|
258 |
// Finally, replace the patterns with the replacements and return the output HTML.
|
259 |
return str_replace(array_keys($attrDefaultValues), array_values($attrDefaultValues), $output);
|
260 |
}
|
261 |
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
475 |
<script type="text/javascript">
|
476 |
window.fluentFormVars.forms["fluentform_<?php echo $form_id;?>"] = <?php echo $vars; ?>;
|
477 |
</script>
|
478 |
-
|
479 |
-
|
480 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
481 |
}
|
7 |
use FluentForm\App\Services\FormBuilder\EditorShortcodeParser;
|
8 |
use FluentForm\App\Services\FormBuilder\Notifications\EmailNotificationActions;
|
9 |
|
10 |
+
class Component
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* FluentForm\Framework\Foundation\Application
|
14 |
+
*
|
15 |
+
* @var $app
|
16 |
+
*/
|
17 |
+
protected $app = null;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Biuld the instance of this class
|
21 |
+
*
|
22 |
+
* @param \FluentForm\Framework\Foundation\Application $app
|
23 |
+
*/
|
24 |
+
public function __construct(Application $app)
|
25 |
+
{
|
26 |
+
$this->app = $app;
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Get all the available components
|
31 |
+
*
|
32 |
+
* @return void
|
33 |
+
* @throws \Exception
|
34 |
+
* @throws \FluentForm\Framework\Exception\UnResolveableEntityException
|
35 |
+
*/
|
36 |
+
public function index()
|
37 |
+
{
|
38 |
+
Acl::verify('fluentform_forms_manager');
|
39 |
+
|
40 |
+
$this->app->doAction(
|
41 |
+
'fluent_editor_init',
|
42 |
+
$components = $this->app->make('components')
|
43 |
+
);
|
44 |
+
|
45 |
+
$editorCompnents = $components->sort()->toArray();
|
46 |
+
$editorCompnents = $this->app->applyFilters('fluent_editor_components', $editorCompnents);
|
47 |
+
$countries = $this->app->load($this->app->appPath('Services/FormBuilder/CountryNames.php'));
|
48 |
+
|
49 |
+
wp_send_json_success(array(
|
50 |
+
'components' => $editorCompnents,
|
51 |
+
'countries' => $countries,
|
52 |
+
'disabled_components' => $this->getDisabledComponents()
|
53 |
+
));
|
54 |
+
exit();
|
55 |
+
}
|
56 |
+
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Get disabled components
|
60 |
+
*
|
61 |
+
* @return array
|
62 |
+
*/
|
63 |
+
private function getDisabledComponents()
|
64 |
+
{
|
65 |
+
$isReCaptchaDisabled = !get_option('_fluentform_reCaptcha_keys_status', false);
|
66 |
+
|
67 |
+
$disabled = array(
|
68 |
+
'recaptcha' => array(
|
69 |
+
'contentComponent' => 'recaptcha',
|
70 |
+
'disabled' => $isReCaptchaDisabled
|
71 |
+
),
|
72 |
+
'input_image' => array(
|
73 |
+
'disabled' => true
|
74 |
+
),
|
75 |
+
'input_file' => array(
|
76 |
+
'disabled' => true
|
77 |
+
),
|
78 |
+
'input_repeat' => array(
|
79 |
+
'disabled' => true
|
80 |
+
),
|
81 |
+
'shortcode' => array(
|
82 |
+
'disabled' => true
|
83 |
+
),
|
84 |
+
'action_hook' => array(
|
85 |
+
'disabled' => true
|
86 |
+
),
|
87 |
+
'form_step' => array(
|
88 |
+
'disabled' => true
|
89 |
+
)
|
90 |
+
);
|
91 |
+
|
92 |
+
if (!defined('FLUENTFORMPRO')) {
|
93 |
+
$disabled['ratings'] = array(
|
94 |
+
'disabled' => true
|
95 |
+
);
|
96 |
}
|
97 |
+
return $this->app->applyFilters('fluentform_disabled_components', $disabled);
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Get available shortcodes for editor
|
102 |
+
*
|
103 |
+
* @return void
|
104 |
+
* @throws \Exception
|
105 |
+
*/
|
106 |
+
public function getEditorShortcodes()
|
107 |
+
{
|
108 |
+
Acl::verify('fluentform_forms_manager');
|
109 |
+
$editor_shortcodes = fluentFormEditorShortCodes();
|
110 |
+
wp_send_json_success(['shortcodes' => $editor_shortcodes], 200);
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Get all available shortcodes for editor
|
115 |
+
*
|
116 |
+
* @return void
|
117 |
+
* @throws \Exception
|
118 |
+
*/
|
119 |
+
public function getAllEditorShortcodes()
|
120 |
+
{
|
121 |
+
Acl::verify('fluentform_forms_manager');
|
122 |
+
wp_send_json(fluentFormGetAllEditorShortCodes(
|
123 |
+
$this->app->request->get('formId')
|
124 |
+
), 200);
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Register the form renderer shortcode
|
129 |
+
*
|
130 |
+
* @return void
|
131 |
+
*/
|
132 |
+
public function addFluentFormShortCode()
|
133 |
+
{
|
134 |
+
$this->app->addShortCode('fluentform', function ($atts, $content) {
|
135 |
+
|
136 |
+
$this->registerScripts();
|
137 |
+
|
138 |
+
$shortcodeDefaults = apply_filters('fluentform_shortcode_defaults', array(
|
139 |
+
'id' => null,
|
140 |
+
'title' => null,
|
141 |
+
'permission' => '',
|
142 |
+
'permission_message' => __('Sorry, You do not have permission to view this form', 'fluentform')
|
143 |
+
), $atts);
|
144 |
+
|
145 |
+
$atts = shortcode_atts($shortcodeDefaults, $atts);
|
146 |
+
|
147 |
+
$form_id = $atts['id'];
|
148 |
+
|
149 |
+
if ($form_id) {
|
150 |
+
$form = wpFluent()->table('fluentform_forms')->find($form_id);
|
151 |
+
} else if ($formTitle = $atts['title']) {
|
152 |
+
$form = wpFluent()->table('fluentform_forms')->where('title', $formTitle)->first();
|
153 |
+
} else {
|
154 |
+
return;
|
155 |
+
}
|
156 |
+
|
157 |
+
if (!$form) {
|
158 |
+
return;
|
159 |
+
}
|
160 |
+
|
161 |
+
if ($atts['permission']) {
|
162 |
+
if (!current_user_can($atts['permission'])) {
|
163 |
+
return "<div id='ff_form_{$form->id}' class='ff_form_not_render'>{$atts['permission_message']}</div>";
|
164 |
+
}
|
165 |
+
}
|
166 |
+
|
167 |
+
$formSettings = wpFluent()
|
168 |
+
->table('fluentform_form_meta')
|
169 |
+
->where('form_id', $form_id)
|
170 |
+
->where('meta_key', 'formSettings')
|
171 |
+
->first();
|
172 |
+
|
173 |
+
if (!$formSettings) {
|
174 |
+
return;
|
175 |
+
}
|
176 |
+
|
177 |
+
$form->fields = json_decode($form->form_fields, true);
|
178 |
+
|
179 |
+
if (!$form->fields['fields']) {
|
180 |
+
return;
|
181 |
+
}
|
182 |
+
|
183 |
+
$form->settings = json_decode($formSettings->value, true);
|
184 |
+
$form = $this->app->applyFilters('fluentform_rendering_form', $form);
|
185 |
+
|
186 |
+
$isRenderable = array(
|
187 |
+
'status' => true,
|
188 |
+
'message' => ''
|
189 |
+
);
|
190 |
+
|
191 |
+
$isRenderable = $this->app->applyFilters('fluentform_is_form_renderable', $isRenderable, $form);
|
192 |
+
|
193 |
+
if (is_array($isRenderable) && !$isRenderable['status']) {
|
194 |
+
return "<div id='ff_form_{$form->id}' class='ff_form_not_render'>{$isRenderable['message']}</div>";
|
195 |
+
}
|
196 |
+
|
197 |
+
$formBuilder = $this->app->make('formBuilder');
|
198 |
+
$output = $formBuilder->build($form);
|
199 |
$output = $this->processOutput($output, $form);
|
200 |
|
201 |
+
wp_enqueue_style(
|
202 |
+
'fluent-form-styles',
|
203 |
+
$this->app->publicUrl('css/fluent-forms-public.css'),
|
204 |
+
array(),
|
205 |
+
FLUENTFORM_VERSION
|
206 |
+
);
|
207 |
+
|
208 |
+
wp_enqueue_style(
|
209 |
+
'fluentform-public-default',
|
210 |
+
$this->app->publicUrl('css/fluentform-public-default.css'),
|
211 |
+
array(),
|
212 |
+
FLUENTFORM_VERSION
|
213 |
+
);
|
214 |
+
|
215 |
+
wp_enqueue_script(
|
216 |
+
'fluent-form-submission',
|
217 |
+
$this->app->publicUrl('js/form-submission.js'),
|
218 |
+
array('jquery'),
|
219 |
+
FLUENTFORM_VERSION,
|
220 |
+
true
|
221 |
+
);
|
222 |
+
|
223 |
+
$form_vars = array(
|
224 |
+
'id' => $form->id,
|
225 |
+
'settings' => $form->settings,
|
226 |
+
'rules' => $formBuilder->validationRules,
|
227 |
+
'do_analytics' => $this->app->applyFilters('fluentform_do_analytics', true)
|
228 |
+
);
|
229 |
+
|
230 |
+
if ($conditionals = $formBuilder->conditions) {
|
231 |
+
wp_enqueue_script(
|
232 |
+
'fluent-form-conditionals',
|
233 |
+
$this->app->publicUrl('js/form-conditionals.js'),
|
234 |
+
array('jquery'),
|
235 |
+
FLUENTFORM_VERSION,
|
236 |
+
true
|
237 |
+
);
|
238 |
+
$form_vars['conditionals'] = $conditionals;
|
239 |
+
}
|
240 |
+
|
241 |
+
wp_localize_script('fluent-form-submission', 'fluentFormVars', array(
|
242 |
+
'ajaxUrl' => admin_url('admin-ajax.php'),
|
243 |
+
'forms' => (Object)array(),
|
244 |
+
'isDisableAnalytics' => apply_filters('fluentform-disabled_analytics', false),
|
245 |
+
'date_i18n' => $this->getDatei18n(),
|
246 |
+
'pro_version' => (defined('FLUENTFORMPRO_VERSION')) ? FLUENTFORMPRO_VERSION : false,
|
247 |
+
'fluentform_version' => FLUENTFORM_VERSION
|
248 |
+
));
|
249 |
+
|
250 |
+
$this->addInlineVars(json_encode($form_vars), $form->id);
|
251 |
+
|
252 |
+
return $output;
|
253 |
+
|
254 |
+
});
|
255 |
+
|
256 |
+
$this->app->addShortCode('fluentform_info', function ($atts) {
|
257 |
+
$shortcodeDefaults = apply_filters('fluentform_info_shortcode_defaults', array(
|
258 |
+
'id' => null,
|
259 |
+
'info' => 'submission_count',
|
260 |
+
'status' => 'all',
|
261 |
+
'with_trashed' => 'no',
|
262 |
+
'substract_form' => 0,
|
263 |
+
'hide_on_zero' => 'no',
|
264 |
+
'date_format' => ''
|
265 |
+
), $atts);
|
266 |
+
$atts = shortcode_atts($shortcodeDefaults, $atts);
|
267 |
+
|
268 |
+
$formId = $atts['id'];
|
269 |
+
|
270 |
+
$form = wpFluent()->table('fluentform_forms')->find($formId);
|
271 |
+
|
272 |
+
if(!$form) {
|
273 |
+
return '';
|
274 |
+
}
|
275 |
+
|
276 |
+
if($atts['info'] == 'submission_count') {
|
277 |
+
$countQuery = wpFluent()->table('fluentform_submissions')
|
278 |
+
->where('form_id', $formId);
|
279 |
+
|
280 |
+
if($atts['status'] != 'trashed' && $atts['with_trashed'] == 'no') {
|
281 |
+
$countQuery = $countQuery->where('status', '!=', 'trashed');
|
282 |
+
}
|
283 |
+
|
284 |
+
if($atts['status'] == 'all') {
|
285 |
+
} else if($atts['status'] == 'favourites') {
|
286 |
+
$countQuery = $countQuery->where('is_favourite', '=', 1);
|
287 |
+
} else {
|
288 |
+
$countQuery = $countQuery->where('status', '=', sanitize_text_field($atts['status']));
|
289 |
+
}
|
290 |
+
|
291 |
+
$total = $countQuery->count();
|
292 |
+
if($atts['substract_form']) {
|
293 |
+
$total = intval($atts['substract_form']) - $total;
|
294 |
+
}
|
295 |
+
|
296 |
+
if($atts['hide_on_zero'] && !$total) {
|
297 |
+
return '';
|
298 |
+
}
|
299 |
+
|
300 |
+
if($atts['hide_on_zero'] == 'yes' && !$total) {
|
301 |
+
return '';
|
302 |
+
}
|
303 |
+
|
304 |
+
return $total;
|
305 |
+
} else if($atts['info'] == 'created_at') {
|
306 |
+
if($atts['date_format']) {
|
307 |
+
$dateFormat = $atts['date_format'];
|
308 |
+
} else {
|
309 |
+
$dateFormat = get_option( 'date_format' ) .' '. get_option( 'time_format' );
|
310 |
+
}
|
311 |
+
return date($dateFormat, strtotime($form->created_at));
|
312 |
+
} else if($atts['info'] == 'updated_at') {
|
313 |
+
if($atts['date_format']) {
|
314 |
+
$dateFormat = $atts['date_format'];
|
315 |
+
} else {
|
316 |
+
$dateFormat = get_option( 'date_format' ) .' '. get_option( 'time_format' );
|
317 |
+
}
|
318 |
+
return date($dateFormat, strtotime($form->updated_at));
|
319 |
+
}
|
320 |
+
|
321 |
+
return '';
|
322 |
+
});
|
323 |
+
|
324 |
+
}
|
325 |
+
|
326 |
+
|
327 |
+
/**
|
328 |
+
* Register the Scripts that the inner components can use
|
329 |
+
*
|
330 |
+
* @return void
|
331 |
+
*/
|
332 |
+
private function registerScripts()
|
333 |
+
{
|
334 |
+
// Date Pickckr Style
|
335 |
+
wp_register_style(
|
336 |
+
'flatpickr',
|
337 |
+
$this->app->publicUrl('libs/flatpickr/flatpickr.min.css')
|
338 |
+
);
|
339 |
+
|
340 |
+
// Date Pickckr Script
|
341 |
+
wp_register_script(
|
342 |
+
'flatpickr',
|
343 |
+
$this->app->publicUrl('libs/flatpickr/flatpickr.js'),
|
344 |
+
array('jquery'),
|
345 |
+
false,
|
346 |
+
true
|
347 |
+
);
|
348 |
+
|
349 |
+
wp_register_script(
|
350 |
+
'selectWoo',
|
351 |
+
$this->app->publicUrl('libs/selectWoo/selectWoo.full.min.js'),
|
352 |
+
array('jquery'),
|
353 |
+
'1.0.6',
|
354 |
+
true
|
355 |
+
);
|
356 |
+
|
357 |
+
wp_register_style(
|
358 |
+
'select2',
|
359 |
+
$this->app->publicUrl('libs/selectWoo/select2.css')
|
360 |
+
);
|
361 |
+
|
362 |
+
|
363 |
}
|
364 |
|
365 |
/**
|
380 |
|
381 |
foreach ($patterns as $pattern) {
|
382 |
// The default value for each pattern will be resolved here.
|
383 |
+
$attrDefaultValues[$pattern] = apply_filters('fluentform_parse_default_value', $pattern, $form);
|
384 |
}
|
|
|
385 |
// Raising an event so that others can hook into it and modify the default values later.
|
386 |
+
$attrDefaultValues = (array)apply_filters('fluentform_parse_default_values', $attrDefaultValues);
|
|
|
387 |
// Finally, replace the patterns with the replacements and return the output HTML.
|
388 |
return str_replace(array_keys($attrDefaultValues), array_values($attrDefaultValues), $output);
|
389 |
}
|
390 |
|
391 |
+
/**
|
392 |
+
* Register renderer actions for compiling each element
|
393 |
+
*
|
394 |
+
* @return void
|
395 |
+
*/
|
396 |
+
public function addRendererActions()
|
397 |
+
{
|
398 |
+
$actionMappings = [
|
399 |
+
'Select@compile' => ['fluentform_render_item_select'],
|
400 |
+
'Rating@compile' => ['fluentform_render_item_ratings'],
|
401 |
+
'Address@compile' => ['fluentform_render_item_address'],
|
402 |
+
'Name@compile' => ['fluentform_render_item_input_name'],
|
403 |
+
'TextArea@compile' => ['fluentform_render_item_textarea'],
|
404 |
+
'DateTime@compile' => ['fluentform_render_item_input_date'],
|
405 |
+
'Recaptcha@compile' => ['fluentform_render_item_recaptcha'],
|
406 |
+
'Container@compile' => ['fluentform_render_item_container'],
|
407 |
+
'CustomHtml@compile' => ['fluentform_render_item_custom_html'],
|
408 |
+
'SectionBreak@compile' => ['fluentform_render_item_section_break'],
|
409 |
+
'SubmitButton@compile' => ['fluentform_render_item_submit_button'],
|
410 |
+
'SelectCountry@compile' => ['fluentform_render_item_select_country'],
|
411 |
+
|
412 |
+
'TermsAndConditions@compile' => [
|
413 |
+
'fluentform_render_item_terms_and_condition',
|
414 |
+
'fluentform_render_item_gdpr_agreement'
|
415 |
+
],
|
416 |
+
|
417 |
+
'TabularGrid@compile' => [
|
418 |
+
'fluentform_render_item_tabular_grid'
|
419 |
+
],
|
420 |
+
|
421 |
+
'Checkable@compile' => [
|
422 |
+
'fluentform_render_item_input_radio',
|
423 |
+
'fluentform_render_item_input_checkbox',
|
424 |
+
],
|
425 |
+
|
426 |
+
'Text@compile' => [
|
427 |
+
'fluentform_render_item_input_url',
|
428 |
+
'fluentform_render_item_input_text',
|
429 |
+
'fluentform_render_item_input_email',
|
430 |
+
'fluentform_render_item_input_number',
|
431 |
+
'fluentform_render_item_input_hidden',
|
432 |
+
'fluentform_render_item_input_password',
|
433 |
+
],
|
434 |
+
];
|
435 |
+
|
436 |
+
$path = 'FluentForm\App\Services\FormBuilder\Components\\';
|
437 |
+
foreach ($actionMappings as $handler => $actions) {
|
438 |
+
foreach ($actions as $action) {
|
439 |
+
$this->app->addAction($action, function () use ($path, $handler) {
|
440 |
+
list($class, $method) = $this->app->parseHandler($path . $handler);
|
441 |
+
call_user_func_array(array($class, $method), func_get_args());
|
442 |
+
}, 10, 2);
|
443 |
+
}
|
444 |
+
}
|
445 |
+
}
|
446 |
+
|
447 |
+
/**
|
448 |
+
* Register dynamic value shortcode parser (filter default value)
|
449 |
+
*
|
450 |
+
* @return void
|
451 |
+
*/
|
452 |
+
public function addFluentFormDefaultValueParser()
|
453 |
+
{
|
454 |
+
$this->app->addFilter('fluentform_parse_default_value', function ($value, $form) {
|
455 |
+
return EditorShortcodeParser::filter($value, $form);
|
456 |
+
}, 10, 2);
|
457 |
+
}
|
458 |
+
|
459 |
+
/**
|
460 |
+
* Register filter to check whether the form is renderable
|
461 |
+
*
|
462 |
+
* @return mixed
|
463 |
+
*/
|
464 |
+
public function addIsRenderableFilter()
|
465 |
+
{
|
466 |
+
$this->app->addFilter('fluentform_is_form_renderable', function ($isRenderable, $form) {
|
467 |
+
$checkables = array('limitNumberOfEntries', 'scheduleForm', 'requireLogin');
|
468 |
+
foreach ($form->settings['restrictions'] as $key => $restrictions) {
|
469 |
+
if (in_array($key, $checkables)) {
|
470 |
+
$isRenderable['status'] = $this->{$key}($restrictions, $form, $isRenderable);
|
471 |
+
if (!$isRenderable['status']) {
|
472 |
+
return $isRenderable;
|
473 |
+
}
|
474 |
+
}
|
475 |
+
}
|
476 |
+
|
477 |
+
return $isRenderable;
|
478 |
+
}, 10, 2);
|
479 |
+
}
|
480 |
+
|
481 |
+
/**
|
482 |
+
* Check if limit is set on form submits and it's valid yet
|
483 |
+
*
|
484 |
+
* @param array $restrictions
|
485 |
+
*
|
486 |
+
* @return bool
|
487 |
+
*/
|
488 |
+
private function limitNumberOfEntries($restrictions, $form, &$isRenderable)
|
489 |
+
{
|
490 |
+
if (!$restrictions['enabled']) {
|
491 |
+
return true;
|
492 |
+
}
|
493 |
+
|
494 |
+
$col = 'created_at';
|
495 |
+
$period = $restrictions['period'];
|
496 |
+
$maxAllowedEntries = $restrictions['numberOfEntries'];
|
497 |
+
$query = wpFluent()->table('fluentform_submissions')
|
498 |
+
->where('form_id', $form->id)
|
499 |
+
->where('status', '!=', 'trashed');
|
500 |
+
|
501 |
+
if ($period == 'day') {
|
502 |
+
$year = "YEAR(`{$col}`) = YEAR(NOW())";
|
503 |
+
$month = "MONTH(`{$col}`) = MONTH(NOW())";
|
504 |
+
$day = "DAY(`{$col}`) = DAY(NOW())";
|
505 |
+
$query->where(wpFluent()->raw("{$year} AND {$month} AND {$day}"));
|
506 |
+
} elseif ($period == 'week') {
|
507 |
+
$query->where(
|
508 |
+
wpFluent()->raw("YEARWEEK(`{$col}`, 1) = YEARWEEK(CURDATE(), 1)")
|
509 |
+
);
|
510 |
+
} elseif ($period == 'month') {
|
511 |
+
$year = "YEAR(`{$col}`) = YEAR(NOW())";
|
512 |
+
$month = "MONTH(`{$col}`) = MONTH(NOW())";
|
513 |
+
$query->where(wpFluent()->raw("{$year} AND {$month}"));
|
514 |
+
} elseif ($period == 'year') {
|
515 |
+
$query->where(wpFluent()->raw("YEAR(`{$col}`) = YEAR(NOW())"));
|
516 |
+
}
|
517 |
+
|
518 |
+
if (!($isAllowed = ($query->count() < $maxAllowedEntries))) {
|
519 |
+
$isRenderable['message'] = $restrictions['limitReachedMsg']
|
520 |
+
? $restrictions['limitReachedMsg']
|
521 |
+
: 'Maximum number of entries exceeded.';
|
522 |
+
}
|
523 |
+
|
524 |
+
return $isAllowed;
|
525 |
+
}
|
526 |
+
|
527 |
+
/**
|
528 |
+
* Check if form has scheduled date and open for submission
|
529 |
+
*
|
530 |
+
* @param array $restrictions
|
531 |
+
*
|
532 |
+
* @return bool
|
533 |
+
*/
|
534 |
+
private function scheduleForm($restrictions, $form, &$isRenderable)
|
535 |
+
{
|
536 |
+
if (!$restrictions['enabled']) {
|
537 |
+
return true;
|
538 |
+
}
|
539 |
+
|
540 |
+
$time = time();
|
541 |
+
$start = strtotime($restrictions['start']);
|
542 |
+
$end = strtotime($restrictions['end']);
|
543 |
+
|
544 |
+
if ($time < $start) {
|
545 |
+
$isRenderable['message'] = $restrictions['pendingMsg']
|
546 |
+
? $restrictions['pendingMsg']
|
547 |
+
: 'Form submission is not started yet.';
|
548 |
+
|
549 |
+
return false;
|
550 |
+
}
|
551 |
+
|
552 |
+
if ($time >= $end) {
|
553 |
+
$isRenderable['message'] = $restrictions['expiredMsg']
|
554 |
+
? $restrictions['expiredMsg']
|
555 |
+
: 'Form submission is now closed.';
|
556 |
+
|
557 |
+
return false;
|
558 |
+
}
|
559 |
+
|
560 |
+
return true;
|
561 |
+
}
|
562 |
+
|
563 |
+
/**
|
564 |
+
* * Check if form requires loged in user and user is logged in
|
565 |
+
*
|
566 |
+
* @param array $restrictions
|
567 |
+
*
|
568 |
+
* @return bool
|
569 |
+
*/
|
570 |
+
private function requireLogin($restrictions, $form, &$isRenderable)
|
571 |
+
{
|
572 |
+
if (!$restrictions['enabled']) {
|
573 |
+
return true;
|
574 |
+
}
|
575 |
+
|
576 |
+
if (!($isLoggedIn = is_user_logged_in())) {
|
577 |
+
$isRenderable['message'] = $restrictions['requireLoginMsg']
|
578 |
+
? $restrictions['requireLoginMsg']
|
579 |
+
: 'You must be logged in to submit the form.';
|
580 |
+
}
|
581 |
+
|
582 |
+
return $isLoggedIn;
|
583 |
+
}
|
584 |
+
|
585 |
+
/**
|
586 |
+
* Register fluentform_submission_inserted actions
|
587 |
+
*
|
588 |
+
* @return void
|
589 |
+
*/
|
590 |
+
public function addFluentformSubmissionInsertedFilter()
|
591 |
+
{
|
592 |
+
if (!fluentFormIsHandlingSubmission()) {
|
593 |
+
return;
|
594 |
+
}
|
595 |
+
|
596 |
+
(new EmailNotificationActions($this->app))->register();
|
597 |
+
}
|
598 |
+
|
599 |
+
/**
|
600 |
+
* Add inline scripts [Add localized script using same var]
|
601 |
+
*
|
602 |
+
* @param array $vars
|
603 |
+
* @param int $form_id
|
604 |
+
*
|
605 |
+
* @return void
|
606 |
+
*/
|
607 |
+
private function addInlineVars($vars, $form_id)
|
608 |
+
{
|
609 |
+
add_action('wp_footer', function () use ($vars, $form_id) {
|
610 |
+
?>
|
611 |
<script type="text/javascript">
|
612 |
window.fluentFormVars.forms["fluentform_<?php echo $form_id;?>"] = <?php echo $vars; ?>;
|
613 |
</script>
|
614 |
+
<?php
|
615 |
+
}, 100);
|
616 |
+
}
|
617 |
+
|
618 |
+
private function getDatei18n()
|
619 |
+
{
|
620 |
+
$i18n = array(
|
621 |
+
'previousMonth' => __('Previous Month', 'fluentform'),
|
622 |
+
'nextMonth' => __('Next Month', 'fluentform'),
|
623 |
+
'months' => [
|
624 |
+
'shorthand' => [
|
625 |
+
__('Jan', 'fluentform'),
|
626 |
+
__('Feb', 'fluentform'),
|
627 |
+
__('Mar', 'fluentform'),
|
628 |
+
__('Apr', 'fluentform'),
|
629 |
+
__('May', 'fluentform'),
|
630 |
+
__('Jun', 'fluentform'),
|
631 |
+
__('Jul', 'fluentform'),
|
632 |
+
__('Aug', 'fluentform'),
|
633 |
+
__('Sep', 'fluentform'),
|
634 |
+
__('Oct', 'fluentform'),
|
635 |
+
__('Nov', 'fluentform'),
|
636 |
+
__('Dec', 'fluentform')
|
637 |
+
],
|
638 |
+
'longhand' => [
|
639 |
+
__('January', 'fluentform'),
|
640 |
+
__('February', 'fluentform'),
|
641 |
+
__('March', 'fluentform'),
|
642 |
+
__('April', 'fluentform'),
|
643 |
+
__('May', 'fluentform'),
|
644 |
+
__('June', 'fluentform'),
|
645 |
+
__('July', 'fluentform'),
|
646 |
+
__('August', 'fluentform'),
|
647 |
+
__('September', 'fluentform'),
|
648 |
+
__('October', 'fluentform'),
|
649 |
+
__('November', 'fluentform'),
|
650 |
+
__('December', 'fluentform')
|
651 |
+
]
|
652 |
+
],
|
653 |
+
'weekdays' => [
|
654 |
+
'longhand' => array(
|
655 |
+
__('Sunday', 'fluentform'),
|
656 |
+
__('Monday', 'fluentform'),
|
657 |
+
__('Tuesday', 'fluentform'),
|
658 |
+
__('Wednesday', 'fluentform'),
|
659 |
+
__('Thursday', 'fluentform'),
|
660 |
+
__('Friday', 'fluentform'),
|
661 |
+
__('Saturday', 'fluentform')
|
662 |
+
),
|
663 |
+
'shorthand' => array(
|
664 |
+
__('Sun', 'fluentform'),
|
665 |
+
__('Mon', 'fluentform'),
|
666 |
+
__('Tue', 'fluentform'),
|
667 |
+
__('Wed', 'fluentform'),
|
668 |
+
__('Thu', 'fluentform'),
|
669 |
+
__('Fri', 'fluentform'),
|
670 |
+
__('Sat', 'fluentform')
|
671 |
+
)
|
672 |
+
],
|
673 |
+
'daysInMonth' => [
|
674 |
+
31,
|
675 |
+
28,
|
676 |
+
31,
|
677 |
+
30,
|
678 |
+
31,
|
679 |
+
30,
|
680 |
+
31,
|
681 |
+
31,
|
682 |
+
30,
|
683 |
+
31,
|
684 |
+
30,
|
685 |
+
31
|
686 |
+
],
|
687 |
+
'rangeSeparator' => __(' to ', 'fluentform'),
|
688 |
+
'weekAbbreviation' => __('Wk', 'fluentform'),
|
689 |
+
'scrollTitle' => __('Scroll to increment', 'fluentform'),
|
690 |
+
'toggleTitle' => __('Click to toggle', 'fluentform'),
|
691 |
+
'amPM' => [
|
692 |
+
__('AM', 'fluentform'),
|
693 |
+
__('PM', 'fluentform')
|
694 |
+
],
|
695 |
+
'yearAriaLabel' => __('Year', 'fluentform')
|
696 |
+
);
|
697 |
+
|
698 |
+
return apply_filters('fluentform/date_i18n', $i18n);
|
699 |
+
}
|
700 |
}
|
app/Modules/Component/ComponentInitTrait.php
ADDED
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace FluentForm\App\Modules\Component;
|
4 |
+
|
5 |
+
trait ComponentInitTrait
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Initialize the component and register the
|
9 |
+
* component by registering actions/filters.
|
10 |
+
*/
|
11 |
+
public function _init()
|
12 |
+
{
|
13 |
+
// Validate certain important required properties
|
14 |
+
$this->_fluentFormValidateComponent();
|
15 |
+
|
16 |
+
// Hook to add component in the editor's components array
|
17 |
+
add_filter('fluent_editor_components', [$this, '_fluentEditorComponenstCallback']);
|
18 |
+
|
19 |
+
// Hook to add search keywords for the component
|
20 |
+
add_filter('fluent_editor_element_search_tags', [$this, '_fluentEditorElementSearchTagsCallback']);
|
21 |
+
|
22 |
+
// Hook for component's editor items/options placement settings
|
23 |
+
add_filter('fluent_editor_element_settings_placement', [$this, '_fluentEditorElementSettingsPlacementCallback']);
|
24 |
+
|
25 |
+
// Hook for component's customization settings
|
26 |
+
add_filter('fluent_editor_element_customization_settings', [$this, '_fluentEditorElementCustomizationSettingsCallback']);
|
27 |
+
|
28 |
+
// Hook for response data preperation on form submission
|
29 |
+
add_filter('fluentform_insert_response_data', [$this, '_fluentformInsertResponseDataCallback'], 10, 3);
|
30 |
+
|
31 |
+
// Hook to add component type in fluentform field types to be available in FormFieldParser.
|
32 |
+
add_filter('fluentform_form_input_types', [$this, '_fluentformFormInputTypesCallback']);
|
33 |
+
|
34 |
+
// Component render/compile hook (for form)
|
35 |
+
add_action('fluentform_render_item_' . $this->element(), [$this, '_elementRenderHookCallback'], 10, 2);
|
36 |
+
|
37 |
+
// Component's used element response transformation hook (for entries)
|
38 |
+
add_filter('fluentform_response_render_' . $this->element(), [$this, '_elementEntryFormatCallback'], 10, 3);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Validate certain required properties
|
43 |
+
*
|
44 |
+
* @return void
|
45 |
+
*/
|
46 |
+
private function _fluentFormValidateComponent()
|
47 |
+
{
|
48 |
+
$name = $this->name();
|
49 |
+
if (!$name || !is_string($name)) {
|
50 |
+
wp_die('The name must be a valid string.');
|
51 |
+
}
|
52 |
+
|
53 |
+
$label = $this->label();
|
54 |
+
if (!$label || !is_string($label)) {
|
55 |
+
wp_die('The label must be a valid string.');
|
56 |
+
}
|
57 |
+
|
58 |
+
$element = $this->element();
|
59 |
+
if (!$element || !is_string($element)) {
|
60 |
+
$elements = 'text_input, text_email, textarea';
|
61 |
+
wp_die("The element must be one of the available elements, i.e: $elements e.t.c.");
|
62 |
+
}
|
63 |
+
|
64 |
+
$template = $this->template();
|
65 |
+
if (!$template || !is_string($template)) {
|
66 |
+
$templates = 'inputText, selectCountry. addressFields';
|
67 |
+
wp_die("The template must be one of the available templates, i.e: $templates e.t.c.");
|
68 |
+
}
|
69 |
+
|
70 |
+
$group = $this->group();
|
71 |
+
$groups = ['general', 'advanced', 'container'];
|
72 |
+
if (!$group || !in_array($group, $groups)) {
|
73 |
+
wp_die('Invalid group, available groups: ' . implode(', ', $groups) . '.');
|
74 |
+
}
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Add the component in fluentform editor's components array.
|
79 |
+
*
|
80 |
+
* @param array $components
|
81 |
+
* @return array $components
|
82 |
+
*/
|
83 |
+
public function _fluentEditorComponenstCallback($components)
|
84 |
+
{
|
85 |
+
$name = $this->name();
|
86 |
+
$label = $this->label();
|
87 |
+
$group = $this->group();
|
88 |
+
$index = $this->index();
|
89 |
+
$element = $this->element();
|
90 |
+
$template = $this->template();
|
91 |
+
$iconClass = $this->elementIconClass();
|
92 |
+
$validationRules = $this->validationRules();
|
93 |
+
|
94 |
+
$settings = $this->settings([
|
95 |
+
'label' => $label,
|
96 |
+
'container_class' => '',
|
97 |
+
'label_placement' => '',
|
98 |
+
'help_message' => '',
|
99 |
+
'admin_field_label' => $label,
|
100 |
+
'conditional_logics' => [],
|
101 |
+
'validation_rules' => $validationRules
|
102 |
+
]);
|
103 |
+
|
104 |
+
$attributes = $this->attributes([
|
105 |
+
'id' => "",
|
106 |
+
'name' => $name,
|
107 |
+
'class' => '',
|
108 |
+
'value' => '',
|
109 |
+
'placeholder' => '',
|
110 |
+
]);
|
111 |
+
|
112 |
+
$editorOptions = $this->options([
|
113 |
+
'title' => $label,
|
114 |
+
'template' => $template,
|
115 |
+
'icon_class' => $iconClass,
|
116 |
+
]);
|
117 |
+
|
118 |
+
if (is_null($index)) {
|
119 |
+
$index = count($components[$group]) + 1;
|
120 |
+
}
|
121 |
+
|
122 |
+
$components[$group][] = [
|
123 |
+
'index' => $index,
|
124 |
+
'element' => $element,
|
125 |
+
'settings' => $settings,
|
126 |
+
'attributes' => $attributes,
|
127 |
+
'editor_options' => $editorOptions,
|
128 |
+
];
|
129 |
+
|
130 |
+
return $components;
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Add the keywords for the component to search in the editor.
|
135 |
+
*
|
136 |
+
* @param array $keywords
|
137 |
+
* @return array $keywords
|
138 |
+
*/
|
139 |
+
public function _fluentEditorElementSearchTagsCallback($keywords)
|
140 |
+
{
|
141 |
+
$newKeywords = $this->searchBy();
|
142 |
+
|
143 |
+
if (!$newKeywords) {
|
144 |
+
return $keywords;
|
145 |
+
}
|
146 |
+
|
147 |
+
$existingKeywords = [];
|
148 |
+
$element = $this->element();
|
149 |
+
if (isset($keywords[$element])) {
|
150 |
+
$existingKeywords = $keywords[$element];
|
151 |
+
}
|
152 |
+
|
153 |
+
$keywords[$element] = array_merge(
|
154 |
+
$existingKeywords, $newKeywords
|
155 |
+
);
|
156 |
+
|
157 |
+
return $keywords;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Configure placements of input customization options in editor.
|
162 |
+
*
|
163 |
+
* @param array $placemenSettings
|
164 |
+
* @return array $placemenSettings
|
165 |
+
*/
|
166 |
+
public function _fluentEditorElementSettingsPlacementCallback($placementSettings)
|
167 |
+
{
|
168 |
+
$default = [
|
169 |
+
'general' => [
|
170 |
+
'label',
|
171 |
+
'label_placement',
|
172 |
+
'admin_field_label',
|
173 |
+
'placeholder',
|
174 |
+
'value',
|
175 |
+
'validation_rules',
|
176 |
+
],
|
177 |
+
'advanced' => [
|
178 |
+
'name',
|
179 |
+
'class',
|
180 |
+
'help_message',
|
181 |
+
'container_class',
|
182 |
+
'conditional_logics',
|
183 |
+
],
|
184 |
+
];
|
185 |
+
|
186 |
+
$placementSettings[$this->element()] = array_merge_recursive(
|
187 |
+
$this->placementSettings($default), $placementSettings
|
188 |
+
);
|
189 |
+
|
190 |
+
return $placementSettings;
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Configure input customization options/items in the editor.
|
195 |
+
*
|
196 |
+
* @param array $customizationSettings
|
197 |
+
* @return array $customizationSettings
|
198 |
+
*/
|
199 |
+
public function _fluentEditorElementCustomizationSettingsCallback($customizationSettings)
|
200 |
+
{
|
201 |
+
return $this->customizationSettings($customizationSettings);
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* Prepare the submission data for the element on Form Submission.
|
206 |
+
*
|
207 |
+
* @param array $formData
|
208 |
+
* @param int $formId
|
209 |
+
* @param array $inputConfigs
|
210 |
+
* @return array $formData
|
211 |
+
*/
|
212 |
+
public function _fluentformInsertResponseDataCallback($formData, $formId, $inputConfigs)
|
213 |
+
{
|
214 |
+
return $this->onSubmit($formData, $formId, $inputConfigs);
|
215 |
+
}
|
216 |
+
|
217 |
+
/**
|
218 |
+
* Add the component type in fluentform field
|
219 |
+
* types to be available in FormFieldParser.
|
220 |
+
*
|
221 |
+
* @param array $types
|
222 |
+
* @return array $types
|
223 |
+
*/
|
224 |
+
public function _fluentformFormInputTypesCallback($types)
|
225 |
+
{
|
226 |
+
return $this->addType($types);
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Render the component.
|
231 |
+
*
|
232 |
+
* @param array $data
|
233 |
+
* @param stdClass $form
|
234 |
+
* @return void
|
235 |
+
*/
|
236 |
+
public function _elementRenderHookCallback($item, $form)
|
237 |
+
{
|
238 |
+
$this->render($item, $form);
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
* Element's entry value transformation.
|
243 |
+
*
|
244 |
+
* @param mixed $value
|
245 |
+
* @param string $field
|
246 |
+
* @param int $formId
|
247 |
+
* @return mixed $value
|
248 |
+
*/
|
249 |
+
public function _elementEntryFormatCallback($value, $field, $formId)
|
250 |
+
{
|
251 |
+
return $this->formatEntryValue($value, $field, $formId);
|
252 |
+
}
|
253 |
+
}
|
app/Modules/DashboardWidgetModule.php
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace FluentForm\App\Modules;
|
4 |
+
|
5 |
+
class DashboardWidgetModule
|
6 |
+
{
|
7 |
+
public function showStat()
|
8 |
+
{
|
9 |
+
global $wpdb;
|
10 |
+
$stats = wpFluent()->table('fluentform_submissions')
|
11 |
+
->select([
|
12 |
+
'fluentform_forms.title',
|
13 |
+
'fluentform_submissions.form_id',
|
14 |
+
wpFluent()->raw('count(' . $wpdb->prefix . 'fluentform_submissions.id) as total'),
|
15 |
+
wpFluent()->raw('max(' . $wpdb->prefix . 'fluentform_submissions.id) as max_id'),
|
16 |
+
])
|
17 |
+
->orderBy('max_id', 'DESC')
|
18 |
+
->groupBy('fluentform_submissions.form_id')
|
19 |
+
->join('fluentform_forms', 'fluentform_forms.id', '=', 'fluentform_submissions.form_id')
|
20 |
+
->limit(10)
|
21 |
+
->get();
|
22 |
+
|
23 |
+
if (!$stats) {
|
24 |
+
echo 'You can see your submission stats here';
|
25 |
+
return;
|
26 |
+
}
|
27 |
+
|
28 |
+
$this->printStats($stats);
|
29 |
+
return;
|
30 |
+
}
|
31 |
+
|
32 |
+
private function printStats($stats)
|
33 |
+
{
|
34 |
+
?>
|
35 |
+
<ul class="ff_dashboard_stats">
|
36 |
+
<?php foreach ($stats as $stat): ?>
|
37 |
+
<li>
|
38 |
+
<a href="<?php echo admin_url('admin.php?page=fluent_forms&route=entries&form_id=' . $stat->form_id); ?>">
|
39 |
+
<?php echo $stat->title; ?>
|
40 |
+
<span class="ff_total"><?php echo $stat->total; ?></span>
|
41 |
+
</a>
|
42 |
+
</li>
|
43 |
+
<?php endforeach; ?>
|
44 |
+
</ul>
|
45 |
+
<?php if (!defined('NINJA_TABLES_DIR_URL')): ?>
|
46 |
+
<div class="ff_recommended_plugin">
|
47 |
+
Recommended Plugin: <b>Ninja Tables</b> - Best Table Plugin for WP -
|
48 |
+
<a href="<?php echo $this->getInstallUrl('ninja-tables'); ?>">Install</a>
|
49 |
+
| <a target="_blank" rel="noopener" href="https://wordpress.org/plugins/ninja-tables/">Learn More</a>
|
50 |
+
</div>
|
51 |
+
<?php elseif (!defined('ENHANCED_BLOCKS_VERSION')) : ?>
|
52 |
+
<div class="ff_recommended_plugin">
|
53 |
+
Recommended Plugin: <b>Enhanced Blocks – Page Builder Blocks for Gutenberg</b> <br />
|
54 |
+
<a href="<?php echo $this->getInstallUrl('enhanced-blocks'); ?>">Install</a>
|
55 |
+
| <a target="_blank" rel="noopener" href="https://wordpress.org/plugins/enhanced-blocks/">Learn More</a>
|
56 |
+
</div>
|
57 |
+
<?php endif; ?>
|
58 |
+
<style>
|
59 |
+
ul.ff_dashboard_stats {
|
60 |
+
margin: 0;
|
61 |
+
padding: 0;
|
62 |
+
list-style: none;
|
63 |
+
}
|
64 |
+
|
65 |
+
ul.ff_dashboard_stats li {
|
66 |
+
padding: 8px 12px;
|
67 |
+
border-bottom: 1px solid #eeeeee;
|
68 |
+
margin: 0 -12px;
|
69 |
+
cursor: pointer;
|
70 |
+
}
|
71 |
+
|
72 |
+
ul.ff_dashboard_stats li:hover {
|
73 |
+
background: #fafafa;
|
74 |
+
border-bottom: 1px solid #eeeeee;
|
75 |
+
}
|
76 |
+
|
77 |
+
ul.ff_dashboard_stats li:hover a {
|
78 |
+
color: black;
|
79 |
+
}
|
80 |
+
|
81 |
+
ul.ff_dashboard_stats li:nth-child(2n+2) {
|
82 |
+
background: #f9f9f9;
|
83 |
+
}
|
84 |
+
|
85 |
+
ul.ff_dashboard_stats li span.ff_total {
|
86 |
+
float: right;
|
87 |
+
}
|
88 |
+
|
89 |
+
ul.ff_dashboard_stats li a {
|
90 |
+
display: block;
|
91 |
+
color: #0073aa;
|
92 |
+
font-weight: 500;
|
93 |
+
font-size: 105%;
|
94 |
+
}
|
95 |
+
|
96 |
+
.ff_recommended_plugin {
|
97 |
+
padding: 15px 0px 0px;
|
98 |
+
}
|
99 |
+
.ff_recommended_plugin a {
|
100 |
+
font-weight: bold;
|
101 |
+
font-size: 110%;
|
102 |
+
}
|
103 |
+
</style>
|
104 |
+
<?php
|
105 |
+
}
|
106 |
+
|
107 |
+
private function getInstallUrl($plugin)
|
108 |
+
{
|
109 |
+
return wp_nonce_url(
|
110 |
+
self_admin_url('update.php?action=install-plugin&plugin=' . $plugin),
|
111 |
+
'install-plugin_' . $plugin
|
112 |
+
);
|
113 |
+
}
|
114 |
+
}
|
app/Modules/DocumentationModule.php
CHANGED
@@ -25,50 +25,50 @@ class DocumentationModule
|
|
25 |
{
|
26 |
$guides = array(
|
27 |
array(
|
28 |
-
'title' => 'Adding a new form',
|
29 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/create-fluent-form/'
|
30 |
),
|
31 |
array(
|
32 |
-
'title' => 'Setting up form submission confirmation',
|
33 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/submission-confirmation-message/'
|
34 |
),
|
35 |
array(
|
36 |
-
'title' => 'Form layout settings',
|
37 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/form-layout-settings/'
|
38 |
),
|
39 |
array(
|
40 |
-
'title' => 'Conditional logics',
|
41 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/advanced-features-functionalities-in-wp-fluent-form/conditional-logic-fluent-form/'
|
42 |
),
|
43 |
array(
|
44 |
-
'title' => 'Managing form submissions',
|
45 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/managing-submitted-entries/'
|
46 |
),
|
47 |
array(
|
48 |
-
'title' => 'Setting up email notifications',
|
49 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/email-notification/'
|
50 |
),
|
51 |
array(
|
52 |
-
'title' => 'MailChimp integration',
|
53 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/integrations-availabel-in-wp-fluent-form/mailchimp-integration/'
|
54 |
),
|
55 |
array(
|
56 |
-
'title' => 'Slack integration',
|
57 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/integrations-availabel-in-wp-fluent-form/slack-integration-fluentform/'
|
58 |
),
|
59 |
array(
|
60 |
-
'title' => 'Form restrictions and scheduling',
|
61 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/advanced-features-functionalities-in-wp-fluent-form/form-restrictions-scheduling/'
|
62 |
),
|
63 |
array(
|
64 |
-
'title' => 'Setup conditional confirmation messages',
|
65 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/advanced-features-functionalities-in-wp-fluent-form/conditional-confirmation-wp-fluent-form/'
|
66 |
),
|
67 |
array(
|
68 |
-
'title' => 'Predefined form fields',
|
69 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/field-types/'
|
70 |
),
|
71 |
);
|
72 |
return apply_filters('fluentform_user_guide_links', $guides);
|
73 |
}
|
74 |
-
}
|
25 |
{
|
26 |
$guides = array(
|
27 |
array(
|
28 |
+
'title' => __('Adding a new form', 'fluentform'),
|
29 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/create-fluent-form/'
|
30 |
),
|
31 |
array(
|
32 |
+
'title' => __('Setting up form submission confirmation', 'fluentform'),
|
33 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/submission-confirmation-message/'
|
34 |
),
|
35 |
array(
|
36 |
+
'title' => __('Form layout settings', 'fluentform'),
|
37 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/form-layout-settings/'
|
38 |
),
|
39 |
array(
|
40 |
+
'title' => __('Conditional logics', 'fluentform'),
|
41 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/advanced-features-functionalities-in-wp-fluent-form/conditional-logic-fluent-form/'
|
42 |
),
|
43 |
array(
|
44 |
+
'title' => __('Managing form submissions', 'fluentform'),
|
45 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/managing-submitted-entries/'
|
46 |
),
|
47 |
array(
|
48 |
+
'title' => __('Setting up email notifications', 'fluentform'),
|
49 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/getting-started/email-notification/'
|
50 |
),
|
51 |
array(
|
52 |
+
'title' => __('MailChimp integration', 'fluentform'),
|
53 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/integrations-availabel-in-wp-fluent-form/mailchimp-integration/'
|
54 |
),
|
55 |
array(
|
56 |
+
'title' => __('Slack integration', 'fluentform'),
|
57 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/integrations-availabel-in-wp-fluent-form/slack-integration-fluentform/'
|
58 |
),
|
59 |
array(
|
60 |
+
'title' => __('Form restrictions and scheduling', 'fluentform'),
|
61 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/advanced-features-functionalities-in-wp-fluent-form/form-restrictions-scheduling/'
|
62 |
),
|
63 |
array(
|
64 |
+
'title' => __('Setup conditional confirmation messages', 'fluentform'),
|
65 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/advanced-features-functionalities-in-wp-fluent-form/conditional-confirmation-wp-fluent-form/'
|
66 |
),
|
67 |
array(
|
68 |
+
'title' => __('Predefined form fields', 'fluentform'),
|
69 |
'link' => 'https://wpmanageninja.com/docs/fluent-form/field-types/'
|
70 |
),
|
71 |
);
|
72 |
return apply_filters('fluentform_user_guide_links', $guides);
|
73 |
}
|
74 |
+
}
|
app/Modules/Entries/Entries.php
CHANGED
@@ -77,6 +77,12 @@ class Entries extends EntryQuery
|
|
77 |
$this->status = $entryType;
|
78 |
}
|
79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
$form = $this->formModel->find($formId);
|
81 |
$formMeta = $this->getFormInputsAndLabels($form);
|
82 |
$formLabels = $formMeta['labels'];
|
@@ -192,16 +198,29 @@ class Entries extends EntryQuery
|
|
192 |
{
|
193 |
$formId = intval($this->request->get('form_id'));
|
194 |
$entry_id = intval($this->request->get('entry_id'));
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
|
196 |
$notes = $this->responseMetaModel
|
197 |
->where('form_id', $formId)
|
198 |
->where('response_id', $entry_id)
|
199 |
-
->
|
200 |
->get();
|
201 |
|
202 |
foreach ($notes as $note) {
|
203 |
if ($note->user_id) {
|
204 |
$note->pemalink = get_edit_user_link($note->user_id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
} else {
|
206 |
$note->pemalink = false;
|
207 |
}
|
@@ -269,11 +288,16 @@ class Entries extends EntryQuery
|
|
269 |
$entryId = intval($this->request->get('entry_id'));
|
270 |
$newStatus = sanitize_text_field($this->request->get('status'));
|
271 |
|
272 |
-
|
273 |
->where('form_id', $formId)
|
274 |
->where('id', $entryId)
|
275 |
->delete();
|
276 |
|
|
|
|
|
|
|
|
|
|
|
277 |
wp_send_json_success([
|
278 |
'message' => __('Item Successfully deleted', 'fluentform'),
|
279 |
'status' => $newStatus
|
77 |
$this->status = $entryType;
|
78 |
}
|
79 |
|
80 |
+
$dateRange = $this->request->get('date_range');
|
81 |
+
if($dateRange) {
|
82 |
+
$this->startDate = $dateRange[0];
|
83 |
+
$this->endDate = $dateRange[1];
|
84 |
+
}
|
85 |
+
|
86 |
$form = $this->formModel->find($formId);
|
87 |
$formMeta = $this->getFormInputsAndLabels($form);
|
88 |
$formLabels = $formMeta['labels'];
|
198 |
{
|
199 |
$formId = intval($this->request->get('form_id'));
|
200 |
$entry_id = intval($this->request->get('entry_id'));
|
201 |
+
$apiLog = sanitize_text_field($this->request->get('api_log')) == 'yes';
|
202 |
+
|
203 |
+
$metaKeys = ['_notes'];
|
204 |
+
if($apiLog) {
|
205 |
+
$metaKeys[] = 'api_log';
|
206 |
+
}
|
207 |
|
208 |
$notes = $this->responseMetaModel
|
209 |
->where('form_id', $formId)
|
210 |
->where('response_id', $entry_id)
|
211 |
+
->whereIn('meta_key', $metaKeys)
|
212 |
->get();
|
213 |
|
214 |
foreach ($notes as $note) {
|
215 |
if ($note->user_id) {
|
216 |
$note->pemalink = get_edit_user_link($note->user_id);
|
217 |
+
$user = get_user_by('ID', $note->user_id);
|
218 |
+
if($user) {
|
219 |
+
$note->created_by = $user->display_name;
|
220 |
+
} else {
|
221 |
+
$note->created_by = __('FluentForm Bot', 'fluentform');
|
222 |
+
}
|
223 |
+
|
224 |
} else {
|
225 |
$note->pemalink = false;
|
226 |
}
|
288 |
$entryId = intval($this->request->get('entry_id'));
|
289 |
$newStatus = sanitize_text_field($this->request->get('status'));
|
290 |
|
291 |
+
wpFluent()->table('fluentform_submissions')
|
292 |
->where('form_id', $formId)
|
293 |
->where('id', $entryId)
|
294 |
->delete();
|
295 |
|
296 |
+
wpFluent()->table('fluentform_submission_meta')
|
297 |
+
->where('response_id', $entryId)
|
298 |
+
->where('form_id', $formId)
|
299 |
+
->delete();
|
300 |
+
|
301 |
wp_send_json_success([
|
302 |
'message' => __('Item Successfully deleted', 'fluentform'),
|
303 |
'status' => $newStatus
|
app/Modules/Entries/EntryQuery.php
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
<?php
|
2 |
|
3 |
namespace FluentForm\App\Modules\Entries;
|
4 |
|
@@ -23,6 +23,9 @@ class EntryQuery
|
|
23 |
protected $search = false;
|
24 |
protected $wheres = array();
|
25 |
|
|
|
|
|
|
|
26 |
public function __construct()
|
27 |
{
|
28 |
$this->request = App::make('request');
|
@@ -55,14 +58,25 @@ class EntryQuery
|
|
55 |
}
|
56 |
}
|
57 |
|
|
|
|
|
|
|
|
|
|
|
58 |
if ($this->search) {
|
59 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
}
|
61 |
|
62 |
-
if($this->wheres) {
|
63 |
-
foreach ($this->wheres as
|
64 |
-
if(is_array($where) && count($where > 1)) {
|
65 |
-
if(count($where) > 2) {
|
66 |
$column = $where[0];
|
67 |
$operator = $where[1];
|
68 |
$value = $where[2];
|
@@ -154,10 +168,11 @@ class EntryQuery
|
|
154 |
$counts['all'] -= $counts['trashed'];
|
155 |
}
|
156 |
$favorites = wpFluent()
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
|
|
161 |
$counts['favourites'] = $favorites;
|
162 |
return $counts;
|
163 |
}
|
1 |
+
<?php
|
2 |
|
3 |
namespace FluentForm\App\Modules\Entries;
|
4 |
|
23 |
protected $search = false;
|
24 |
protected $wheres = array();
|
25 |
|
26 |
+
protected $startDate;
|
27 |
+
protected $endDate;
|
28 |
+
|
29 |
public function __construct()
|
30 |
{
|
31 |
$this->request = App::make('request');
|
58 |
}
|
59 |
}
|
60 |
|
61 |
+
if ($this->startDate && $this->endDate) {
|
62 |
+
$query->where('created_at', '>=', $this->startDate);
|
63 |
+
$query->where('created_at', '<=', $this->endDate);
|
64 |
+
}
|
65 |
+
|
66 |
if ($this->search) {
|
67 |
+
$searchString = $this->search;
|
68 |
+
$query->where(function ($q) use ($searchString) {
|
69 |
+
$q->where('id', 'LIKE', "%{$searchString}%")
|
70 |
+
->orWhere('response', 'LIKE', "%{$searchString}%")
|
71 |
+
->orWhere('status', 'LIKE', "%{$searchString}%")
|
72 |
+
->orWhere('created_at', 'LIKE', "%{$searchString}%");
|
73 |
+
});
|
74 |
}
|
75 |
|
76 |
+
if ($this->wheres) {
|
77 |
+
foreach ($this->wheres as $where) {
|
78 |
+
if (is_array($where) && count($where > 1)) {
|
79 |
+
if (count($where) > 2) {
|
80 |
$column = $where[0];
|
81 |
$operator = $where[1];
|
82 |
$value = $where[2];
|
168 |
$counts['all'] -= $counts['trashed'];
|
169 |
}
|
170 |
$favorites = wpFluent()
|
171 |
+
->table('fluentform_submissions')
|
172 |
+
->where('form_id', $form_id)
|
173 |
+
->where('is_favourite', 1)
|
174 |
+
->where('status', '!=', 'trashed')
|
175 |
+
->count();
|
176 |
$counts['favourites'] = $favorites;
|
177 |
return $counts;
|
178 |
}
|
app/Modules/Entries/Export.php
CHANGED
@@ -2,6 +2,7 @@
|
|
2 |
|
3 |
namespace FluentForm\App\Modules\Entries;
|
4 |
|
|
|
5 |
use SplTempFileObject;
|
6 |
use FluentForm\App\Modules\Form\FormDataParser;
|
7 |
use FluentForm\Framework\Foundation\Application;
|
@@ -15,6 +16,8 @@ class Export
|
|
15 |
*/
|
16 |
protected $request;
|
17 |
|
|
|
|
|
18 |
/**
|
19 |
* Export constructor.
|
20 |
*
|
@@ -23,6 +26,7 @@ class Export
|
|
23 |
public function __construct(Application $application)
|
24 |
{
|
25 |
$this->request = $application->request;
|
|
|
26 |
}
|
27 |
|
28 |
/**
|
@@ -32,44 +36,147 @@ class Export
|
|
32 |
*/
|
33 |
public function index()
|
34 |
{
|
35 |
-
|
|
|
|
|
36 |
define('FLUENTFORM_DOING_CSV_EXPORT', true);
|
37 |
}
|
38 |
-
|
39 |
$formId = intval($this->request->get('form_id'));
|
40 |
|
41 |
$form = wpFluent()->table('fluentform_forms')->find($formId);
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
$formInputs = FormFieldsParser::getEntryInputs($form, array('admin_label', 'raw'));
|
44 |
-
|
45 |
$inputLabels = FormFieldsParser::getAdminLabels($form, $formInputs);
|
46 |
-
|
47 |
-
$submissions = wpFluent()->table('fluentform_submissions')->where('form_id', $formId)->get();
|
48 |
|
49 |
-
$submissions =
|
50 |
|
|
|
51 |
$exportData = [];
|
52 |
|
53 |
foreach ($submissions as $submission) {
|
54 |
-
$
|
55 |
-
|
56 |
$temp = [];
|
57 |
-
|
58 |
foreach ($inputLabels as $field => $label) {
|
59 |
-
$temp[] = FormDataParser::formatValue(Arr::get($
|
60 |
}
|
61 |
-
|
|
|
|
|
62 |
$exportData[] = $temp;
|
63 |
}
|
64 |
|
65 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
|
67 |
-
$
|
68 |
-
|
69 |
-
$
|
70 |
-
|
71 |
-
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
die();
|
74 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
}
|
2 |
|
3 |
namespace FluentForm\App\Modules\Entries;
|
4 |
|
5 |
+
use FluentForm\App\Modules\Acl\Acl;
|
6 |
use SplTempFileObject;
|
7 |
use FluentForm\App\Modules\Form\FormDataParser;
|
8 |
use FluentForm\Framework\Foundation\Application;
|
16 |
*/
|
17 |
protected $request;
|
18 |
|
19 |
+
protected $app;
|
20 |
+
|
21 |
/**
|
22 |
* Export constructor.
|
23 |
*
|
26 |
public function __construct(Application $application)
|
27 |
{
|
28 |
$this->request = $application->request;
|
29 |
+
$this->app = $application;
|
30 |
}
|
31 |
|
32 |
/**
|
36 |
*/
|
37 |
public function index()
|
38 |
{
|
39 |
+
Acl::verify('fluentform_entries_viewer');
|
40 |
+
|
41 |
+
if (!defined('FLUENTFORM_DOING_CSV_EXPORT')) {
|
42 |
define('FLUENTFORM_DOING_CSV_EXPORT', true);
|
43 |
}
|
44 |
+
|
45 |
$formId = intval($this->request->get('form_id'));
|
46 |
|
47 |
$form = wpFluent()->table('fluentform_forms')->find($formId);
|
48 |
+
|
49 |
+
|
50 |
+
if (!$form) {
|
51 |
+
exit('No Form Found');
|
52 |
+
}
|
53 |
+
|
54 |
+
$type = sanitize_text_field($this->request->get('format', 'csv'));
|
55 |
+
if (!in_array($type, ['csv', 'ods', 'xlsx', 'json'])) {
|
56 |
+
exit('Invalid requested format');
|
57 |
+
}
|
58 |
+
|
59 |
+
if ($type == 'json') {
|
60 |
+
$this->exportAsJSON($form);
|
61 |
+
}
|
62 |
+
|
63 |
$formInputs = FormFieldsParser::getEntryInputs($form, array('admin_label', 'raw'));
|
64 |
+
|
65 |
$inputLabels = FormFieldsParser::getAdminLabels($form, $formInputs);
|
|
|
|
|
66 |
|
67 |
+
$submissions = $this->getSubmissions($formId);
|
68 |
|
69 |
+
$submissions = FormDataParser::parseFormEntries($submissions, $form, $formInputs);
|
70 |
$exportData = [];
|
71 |
|
72 |
foreach ($submissions as $submission) {
|
73 |
+
$submission->response = json_decode($submission->response, true);
|
|
|
74 |
$temp = [];
|
|
|
75 |
foreach ($inputLabels as $field => $label) {
|
76 |
+
$temp[] = FormDataParser::formatValue(Arr::get($submission->user_inputs, $field));
|
77 |
}
|
78 |
+
$temp[] = $submission->id;
|
79 |
+
$temp[] = $submission->status;
|
80 |
+
$temp[] = $submission->created_at;
|
81 |
$exportData[] = $temp;
|
82 |
}
|
83 |
|
84 |
+
$extraLabels = [
|
85 |
+
'entry_id',
|
86 |
+
'entry_status',
|
87 |
+
'created_at'
|
88 |
+
];
|
89 |
+
$inputLabels = array_merge($inputLabels, $extraLabels);
|
90 |
+
|
91 |
+
|
92 |
+
$data = array_merge([array_values($inputLabels)], $exportData);
|
93 |
+
|
94 |
+
$data = apply_filters('fluentform_export_data', $data, $form, $exportData, $inputLabels);
|
95 |
+
|
96 |
|
97 |
+
$fileName = sanitize_title($form->title, 'export', 'view') . '-' . date('Y-m-d');
|
98 |
+
|
99 |
+
$this->downloadOfficeDoc($data, $type, $fileName);
|
100 |
+
}
|
101 |
+
|
102 |
+
|
103 |
+
private function downloadOfficeDoc($data, $type = 'csv', $fileName = null)
|
104 |
+
{
|
105 |
+
$data = array_map(function ($item) {
|
106 |
+
return array_map(function ($itemValue) {
|
107 |
+
if (is_array($itemValue)) {
|
108 |
+
return implode(', ', $itemValue);
|
109 |
+
}
|
110 |
+
return $itemValue;
|
111 |
+
}, $item);
|
112 |
+
}, $data);
|
113 |
+
require_once $this->app->appPath() . 'Services/Spout/Autoloader/autoload.php';
|
114 |
+
$fileName = ($fileName) ? $fileName . '.' . $type : 'export-data-' . date('d-m-Y') . '.' . $type;
|
115 |
+
$writer = \Box\Spout\Writer\WriterFactory::create($type);
|
116 |
+
$writer->openToBrowser($fileName);
|
117 |
+
$writer->addRows($data);
|
118 |
+
$writer->close();
|
119 |
die();
|
120 |
}
|
121 |
+
|
122 |
+
private function exportAsJSON($form)
|
123 |
+
{
|
124 |
+
$formInputs = FormFieldsParser::getEntryInputs($form, array('admin_label', 'raw'));
|
125 |
+
|
126 |
+
$inputLabels = FormFieldsParser::getAdminLabels($form, $formInputs);
|
127 |
+
|
128 |
+
$submissions = $this->getSubmissions($form->id);
|
129 |
+
|
130 |
+
$submissions = FormDataParser::parseFormEntries($submissions, $form, $formInputs);
|
131 |
+
$exportData = [];
|
132 |
+
|
133 |
+
foreach ($submissions as $submission) {
|
134 |
+
$submission->response = json_decode($submission->response, true);
|
135 |
+
}
|
136 |
+
|
137 |
+
header('Content-disposition: attachment; filename=' . sanitize_title($form->title, 'export', 'view') . '-' . date('Y-m-d') . '.json');
|
138 |
+
header('Content-type: application/json');
|
139 |
+
echo json_encode($submissions);
|
140 |
+
exit();
|
141 |
+
}
|
142 |
+
|
143 |
+
private function getSubmissions($formId)
|
144 |
+
{
|
145 |
+
|
146 |
+
$query = wpFluent()->table('fluentform_submissions')
|
147 |
+
->where('form_id', $formId)
|
148 |
+
->orderBy('id', $this->request->get('sort_by', 'DESC'));
|
149 |
+
|
150 |
+
$status = $this->request->get('entry_type');
|
151 |
+
|
152 |
+
$dateRange = $this->request->get('date_range');
|
153 |
+
if($dateRange) {
|
154 |
+
$query->where('created_at', '>=', $dateRange[0]);
|
155 |
+
$query->where('created_at', '<=', $dateRange[1]);
|
156 |
+
}
|
157 |
+
|
158 |
+
if($status == 'trashed') {
|
159 |
+
$query->where('status', 'trashed');
|
160 |
+
} else {
|
161 |
+
$query->where('status', '!=', 'trashed');
|
162 |
+
}
|
163 |
+
|
164 |
+
if ( $status == 'favorite') {
|
165 |
+
$query->where('is_favourite', '1');
|
166 |
+
} else if($status != 'trashed' && $status != 'all') {
|
167 |
+
$query->where('status', $status);
|
168 |
+
}
|
169 |
+
|
170 |
+
$searchString = $this->request->get('search');
|
171 |
+
if ($searchString) {
|
172 |
+
$query->where(function ($q) use ($searchString) {
|
173 |
+
$q->where('id', 'LIKE', "%{$searchString}%")
|
174 |
+
->orWhere('response', 'LIKE', "%{$searchString}%")
|
175 |
+
->orWhere('status', 'LIKE', "%{$searchString}%")
|
176 |
+
->orWhere('created_at', 'LIKE', "%{$searchString}%");
|
177 |
+
});
|
178 |
+
}
|
179 |
+
|
180 |
+
return $query->get();
|
181 |
+
}
|
182 |
}
|
app/Modules/Form/Form.php
CHANGED
@@ -58,66 +58,65 @@ class Form
|
|
58 |
*/
|
59 |
public function index()
|
60 |
{
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
}
|
91 |
|
92 |
wp_send_json($forms, 200);
|
93 |
}
|
94 |
-
|
95 |
private function getFormViewCount($formId)
|
96 |
{
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
}
|
106 |
-
|
107 |
-
private function getSubmissionCount($formID)
|
108 |
{
|
109 |
-
|
110 |
}
|
111 |
-
|
112 |
private function getConversionRate($form)
|
113 |
{
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
}
|
122 |
|
123 |
/**
|
@@ -128,21 +127,20 @@ class Form
|
|
128 |
{
|
129 |
$type = $this->request->get('type', 'form');
|
130 |
$title = $this->request->get('title', 'My New Form');
|
131 |
-
$title .= ' ('.date('Y-m-d').')';
|
132 |
$status = $this->request->get('status', 'published');
|
133 |
$createdBy = get_current_user_id();
|
134 |
-
|
135 |
//$this->validate();
|
136 |
-
|
137 |
$now = date("Y-m-d H:i:s");
|
138 |
-
|
139 |
$insertData = [
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
];
|
147 |
|
148 |
if ($this->formFields) {
|
@@ -151,82 +149,87 @@ class Form
|
|
151 |
|
152 |
$formId = $this->model->insert($insertData);
|
153 |
|
|
|
|
|
|
|
|
|
|
|
154 |
// add default form settings now
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
wpFluent()->table('fluentform_form_meta')
|
166 |
->insert(array(
|
167 |
-
'form_id'
|
168 |
'meta_key' => 'notifications',
|
169 |
-
'value'
|
170 |
));
|
171 |
}
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
wp_send_json_success(array(
|
176 |
-
'formId'
|
177 |
-
'redirect_url' => admin_url('admin.php?page=fluent_forms&form_id='
|
178 |
-
'message'
|
179 |
), 200);
|
180 |
}
|
181 |
-
|
182 |
-
private function getFormsDefaultSettings($formId = false)
|
183 |
{
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
'messageToShow'
|
188 |
-
'customPage'
|
189 |
'samePageFormBehavior' => 'hide_form',
|
190 |
-
'customUrl'
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
'numberOfEntries' => null,
|
196 |
-
'period'
|
197 |
'limitReachedMsg' => null
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
'start'
|
202 |
-
|
203 |
-
'pendingMsg' => "Form submission is not started yet.",
|
204 |
-
'expiredMsg' => "Form submission is now closed."
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
'requireLoginMsg' => null,
|
209 |
-
|
210 |
-
'denyEmptySubmission'
|
211 |
'enabled' => false,
|
212 |
-
'message' => 'Sorry, you cannot submit an empty form. Let\'s hear what you wanna say.'
|
213 |
]
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
'helpMessagePlacement'
|
218 |
'errorMessagePlacement' => 'inline',
|
219 |
-
'cssClassName'
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
}
|
231 |
|
232 |
/**
|
@@ -262,8 +265,8 @@ class Form
|
|
262 |
$this->validate();
|
263 |
|
264 |
$data = [
|
265 |
-
'title'
|
266 |
-
'status'
|
267 |
'updated_at' => date('Y-m-d h:i:s')
|
268 |
];
|
269 |
|
@@ -293,13 +296,62 @@ class Form
|
|
293 |
], 200);
|
294 |
}
|
295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
296 |
/**
|
297 |
* Validate a form only by form title
|
298 |
* @return void
|
299 |
*/
|
300 |
private function validate()
|
301 |
{
|
302 |
-
if (
|
303 |
wp_send_json([
|
304 |
'title' => 'The title field is required.'
|
305 |
], 422);
|
@@ -308,8 +360,8 @@ class Form
|
|
308 |
|
309 |
private function getAdminPermalink($route, $formId)
|
310 |
{
|
311 |
-
|
312 |
-
|
313 |
}
|
314 |
|
315 |
public function getAllForms()
|
58 |
*/
|
59 |
public function index()
|
60 |
{
|
61 |
+
$search = $this->request->get('search');
|
62 |
+
$status = $this->request->get('status');
|
63 |
+
|
64 |
+
$query = $this->model->orderBy('id', 'DESC');
|
65 |
+
|
66 |
+
if ($status && $status != 'all') {
|
67 |
+
$query->where('status', $status);
|
68 |
+
}
|
69 |
+
|
70 |
+
if ($search) {
|
71 |
+
$query->where(function ($q) use ($search) {
|
72 |
+
$q->where('id', 'LIKE', '%' . $search . '%');
|
73 |
+
$q->orWhere('title', 'LIKE', '%' . $search . '%');
|
74 |
+
});
|
75 |
+
}
|
76 |
+
|
77 |
+
$forms = $query->paginate();
|
78 |
+
|
79 |
+
foreach ($forms['data'] as $form) {
|
80 |
+
$form->preview_url = site_url('?fluentform_pages=1&preview_id=' . $form->id) . '#ff_preview';;
|
81 |
+
$form->edit_url = $this->getAdminPermalink('editor', $form->id);
|
82 |
+
$form->settings_url = admin_url('admin.php?page=fluent_forms&form_id=' . $form->id . '&route=settings&sub_route=form_settings#basic_settings');
|
83 |
+
$form->entries_url = $this->getAdminPermalink('entries', $form->id);
|
84 |
+
$form->analytics_url = $this->getAdminPermalink('analytics', $form->id);
|
85 |
+
$form->total_views = $this->getFormViewCount($form->id);
|
86 |
+
$form->total_views = $this->getFormViewCount($form->id);
|
87 |
+
$form->total_Submissions = $this->getSubmissionCount($form->id);
|
88 |
+
$form->conversion = $this->getConversionRate($form);
|
89 |
+
}
|
|
|
90 |
|
91 |
wp_send_json($forms, 200);
|
92 |
}
|
93 |
+
|
94 |
private function getFormViewCount($formId)
|
95 |
{
|
96 |
+
$hasCount = wpFluent()
|
97 |
+
->table('fluentform_form_meta')
|
98 |
+
->where('meta_key', '_total_views')
|
99 |
+
->where('form_id', $formId)
|
100 |
+
->first();
|
101 |
+
if ($hasCount)
|
102 |
+
return intval($hasCount->value);
|
103 |
+
return 0;
|
104 |
}
|
105 |
+
|
106 |
+
private function getSubmissionCount($formID)
|
107 |
{
|
108 |
+
return wpFluent()->table('fluentform_submissions')->where('form_id', $formID)->count();
|
109 |
}
|
110 |
+
|
111 |
private function getConversionRate($form)
|
112 |
{
|
113 |
+
if (!$form->total_Submissions)
|
114 |
+
return 0;
|
115 |
+
|
116 |
+
if (!$form->total_views)
|
117 |
+
return 0;
|
118 |
+
|
119 |
+
return ceil(($form->total_Submissions / $form->total_views) * 100);
|
120 |
}
|
121 |
|
122 |
/**
|
127 |
{
|
128 |
$type = $this->request->get('type', 'form');
|
129 |
$title = $this->request->get('title', 'My New Form');
|
|
|
130 |
$status = $this->request->get('status', 'published');
|
131 |
$createdBy = get_current_user_id();
|
132 |
+
|
133 |
//$this->validate();
|
134 |
+
|
135 |
$now = date("Y-m-d H:i:s");
|
136 |
+
|
137 |
$insertData = [
|
138 |
+
'title' => $title,
|
139 |
+
'type' => $type,
|
140 |
+
'status' => $status,
|
141 |
+
'created_by' => $createdBy,
|
142 |
+
'created_at' => $now,
|
143 |
+
'updated_at' => $now
|
144 |
];
|
145 |
|
146 |
if ($this->formFields) {
|
149 |
|
150 |
$formId = $this->model->insert($insertData);
|
151 |
|
152 |
+
// Ranme the form name here
|
153 |
+
wpFluent()->table('fluentform_forms')->where('id', $formId)->update(array(
|
154 |
+
'title' => $title . ' (#' . $formId . ')'
|
155 |
+
));
|
156 |
+
|
157 |
// add default form settings now
|
158 |
+
$defaultSettings = $this->defaultSettings ?: $this->getFormsDefaultSettings($formId);
|
159 |
+
|
160 |
+
wpFluent()->table('fluentform_form_meta')
|
161 |
+
->insert(array(
|
162 |
+
'form_id' => $formId,
|
163 |
+
'meta_key' => 'formSettings',
|
164 |
+
'value' => json_encode($defaultSettings)
|
165 |
+
));
|
166 |
+
|
167 |
+
if ($this->defaultNotifications) {
|
168 |
wpFluent()->table('fluentform_form_meta')
|
169 |
->insert(array(
|
170 |
+
'form_id' => $formId,
|
171 |
'meta_key' => 'notifications',
|
172 |
+
'value' => json_encode($this->defaultNotifications)
|
173 |
));
|
174 |
}
|
175 |
+
|
176 |
+
do_action('fluentform_inserted_new_form', $formId, $insertData);
|
177 |
+
|
178 |
wp_send_json_success(array(
|
179 |
+
'formId' => $formId,
|
180 |
+
'redirect_url' => admin_url('admin.php?page=fluent_forms&form_id=' . $formId . '&route=editor'),
|
181 |
+
'message' => __('Successfully created a form.', 'fluentform')
|
182 |
), 200);
|
183 |
}
|
184 |
+
|
185 |
+
private function getFormsDefaultSettings($formId = false)
|
186 |
{
|
187 |
+
$defaultSettings = array(
|
188 |
+
'confirmation' => array(
|
189 |
+
'redirectTo' => 'samePage',
|
190 |
+
'messageToShow' => __('Thank you for your message. We will get in touch with you shortly', 'fluentform'),
|
191 |
+
'customPage' => null,
|
192 |
'samePageFormBehavior' => 'hide_form',
|
193 |
+
'customUrl' => null
|
194 |
+
),
|
195 |
+
'restrictions' => array(
|
196 |
+
'limitNumberOfEntries' => array(
|
197 |
+
'enabled' => false,
|
198 |
'numberOfEntries' => null,
|
199 |
+
'period' => 'total',
|
200 |
'limitReachedMsg' => null
|
201 |
+
),
|
202 |
+
'scheduleForm' => array(
|
203 |
+
'enabled' => false,
|
204 |
+
'start' => null,
|
205 |
+
'end' => null,
|
206 |
+
'pendingMsg' => __("Form submission is not started yet.", 'fluentform'),
|
207 |
+
'expiredMsg' => __("Form submission is now closed.", 'fluentform')
|
208 |
+
),
|
209 |
+
'requireLogin' => array(
|
210 |
+
'enabled' => false,
|
211 |
'requireLoginMsg' => null,
|
212 |
+
),
|
213 |
+
'denyEmptySubmission' => [
|
214 |
'enabled' => false,
|
215 |
+
'message' => __('Sorry, you cannot submit an empty form. Let\'s hear what you wanna say.', 'fluentform'),
|
216 |
]
|
217 |
+
),
|
218 |
+
'layout' => array(
|
219 |
+
'labelPlacement' => 'top',
|
220 |
+
'helpMessagePlacement' => 'with_label',
|
221 |
'errorMessagePlacement' => 'inline',
|
222 |
+
'cssClassName' => ''
|
223 |
+
)
|
224 |
+
);
|
225 |
+
|
226 |
+
$globalSettings = get_option('_fluentform_global_form_settings');
|
227 |
+
|
228 |
+
if (isset($globalSettings['layout'])) {
|
229 |
+
$defaultSettings['layout'] = $globalSettings['layout'];
|
230 |
+
}
|
231 |
+
|
232 |
+
return $defaultSettings;
|
233 |
}
|
234 |
|
235 |
/**
|
265 |
$this->validate();
|
266 |
|
267 |
$data = [
|
268 |
+
'title' => $title,
|
269 |
+
'status' => $status,
|
270 |
'updated_at' => date('Y-m-d h:i:s')
|
271 |
];
|
272 |
|
296 |
], 200);
|
297 |
}
|
298 |
|
299 |
+
/**
|
300 |
+
* Duplicate a from
|
301 |
+
* @return void
|
302 |
+
*/
|
303 |
+
public function duplicate()
|
304 |
+
{
|
305 |
+
$formId = absint($this->request->get('formId'));
|
306 |
+
$form = $this->model->where('id', $formId)->first();
|
307 |
+
|
308 |
+
$data = array(
|
309 |
+
'title' => $form->title,
|
310 |
+
'status' => $form->status,
|
311 |
+
'appearance_settings' => $form->appearance_settings,
|
312 |
+
'form_fields' => $form->form_fields,
|
313 |
+
'has_payment' => $form->has_payment,
|
314 |
+
'conditions' => $form->conditions,
|
315 |
+
'created_by' => get_current_user_id(),
|
316 |
+
'created_at' => date('Y-m-d H:i:s'),
|
317 |
+
'updated_at' => date('Y-m-d H:i:s')
|
318 |
+
);
|
319 |
+
|
320 |
+
$newFormId = $this->model->insert($data);
|
321 |
+
|
322 |
+
// Ranme the form name here
|
323 |
+
wpFluent()->table('fluentform_forms')
|
324 |
+
->where('id', $newFormId)
|
325 |
+
->update(array(
|
326 |
+
'title' => $form->title.' (#'.$newFormId.')'
|
327 |
+
));
|
328 |
+
|
329 |
+
$formMetas = wpFluent()->table('fluentform_form_meta')
|
330 |
+
->where('form_id', $formId)
|
331 |
+
->get();
|
332 |
+
foreach ($formMetas as $meta) {
|
333 |
+
$metaData = [
|
334 |
+
'meta_key' => $meta->meta_key,
|
335 |
+
'value' => $meta->value,
|
336 |
+
'form_id' => $newFormId
|
337 |
+
];
|
338 |
+
wpFluent()->table('fluentform_form_meta')->insert($metaData);
|
339 |
+
}
|
340 |
+
|
341 |
+
wp_send_json([
|
342 |
+
'message' => __('Form has been successfully duplicated.', 'fluentform'),
|
343 |
+
'form_id' => $newFormId,
|
344 |
+
'redirect' => admin_url('admin.php?page=fluent_forms&route=editor&form_id=' . $newFormId)
|
345 |
+
], 200);
|
346 |
+
}
|
347 |
+
|
348 |
/**
|
349 |
* Validate a form only by form title
|
350 |
* @return void
|
351 |
*/
|
352 |
private function validate()
|
353 |
{
|
354 |
+
if (!$this->request->get('title')) {
|
355 |
wp_send_json([
|
356 |
'title' => 'The title field is required.'
|
357 |
], 422);
|
360 |
|
361 |
private function getAdminPermalink($route, $formId)
|
362 |
{
|
363 |
+
$baseUrl = admin_url('admin.php?page=fluent_forms');
|
364 |
+
return $baseUrl . '&route=' . $route . "&form_id=" . $formId;
|
365 |
}
|
366 |
|
367 |
public function getAllForms()
|
app/Modules/Form/FormHandler.php
CHANGED
@@ -153,6 +153,8 @@ class FormHandler
|
|
153 |
$confirmation
|
154 |
);
|
155 |
|
|
|
|
|
156 |
wp_send_json_success([
|
157 |
'insert_id' => $insertId,
|
158 |
'result' => $returnData
|
@@ -209,7 +211,7 @@ class FormHandler
|
|
209 |
{
|
210 |
$formId = $this->form->id;
|
211 |
|
212 |
-
$shouldVerifyNonce = $this->app->applyFilters('fluentform_nonce_verify',
|
213 |
|
214 |
if ($shouldVerifyNonce) {
|
215 |
$nonce = Arr::get($this->formData, '_fluentform_'.$formId.'_fluentformnonce');
|
153 |
$confirmation
|
154 |
);
|
155 |
|
156 |
+
do_action('fluenform_before_submission_confirmation', $insertId, $this->formData, $this->form);
|
157 |
+
|
158 |
wp_send_json_success([
|
159 |
'insert_id' => $insertId,
|
160 |
'result' => $returnData
|
211 |
{
|
212 |
$formId = $this->form->id;
|
213 |
|
214 |
+
$shouldVerifyNonce = $this->app->applyFilters('fluentform_nonce_verify', false, $formId);
|
215 |
|
216 |
if ($shouldVerifyNonce) {
|
217 |
$nonce = Arr::get($this->formData, '_fluentform_'.$formId.'_fluentformnonce');
|
app/Modules/Form/Predefined.php
CHANGED
@@ -13,24 +13,144 @@ class Predefined extends Form
|
|
13 |
*/
|
14 |
private function getPredefinedForms() {
|
15 |
return array(
|
16 |
-
|
17 |
-
'screenshot' => App::publicUrl('img/forms/
|
18 |
'createable' => true,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
'brief' => 'A simple contact form for your site.',
|
20 |
-
'json' => '[{"id":"
|
21 |
),
|
22 |
-
'support_form'
|
23 |
-
'screenshot' => App::publicUrl('img/forms/support_form.
|
24 |
'createable' => true,
|
|
|
25 |
'brief' => 'Using this support form users can ask questions.',
|
26 |
-
'json' => '[{"id":"
|
27 |
),
|
28 |
-
'event_registration_form'
|
29 |
-
'screenshot' => App::publicUrl('img/forms/
|
30 |
'createable' => true,
|
|
|
31 |
'brief' => 'Using this registration form you can enable your event to achieve it\'s goal.',
|
32 |
-
'json' => '[{"id":"
|
33 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
);
|
35 |
}
|
36 |
|
@@ -39,6 +159,9 @@ class Predefined extends Form
|
|
39 |
*/
|
40 |
public function all()
|
41 |
{
|
|
|
|
|
|
|
42 |
$data = array ();
|
43 |
foreach ($this->getPredefinedForms() as $key => $item) {
|
44 |
$jsonArray = json_decode($item['json'], true);
|
@@ -46,6 +169,7 @@ class Predefined extends Form
|
|
46 |
|
47 |
$data[$key] = array (
|
48 |
'brief' => $item['brief'],
|
|
|
49 |
'title' => $jsonArray['title'],
|
50 |
'buy_url' => 'https://wpfluentform.com/pro',
|
51 |
'screenshot' => $item['screenshot'],
|
13 |
*/
|
14 |
private function getPredefinedForms() {
|
15 |
return array(
|
16 |
+
'blank_form' => array(
|
17 |
+
'screenshot' => App::publicUrl('img/forms/new_blank.png'),
|
18 |
'createable' => true,
|
19 |
+
'title' => 'Blank Form',
|
20 |
+
'brief' => '',
|
21 |
+
'json' => '[{"id":"32","title":"New Blank Form","form":null,"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}}}]'
|
22 |
+
),
|
23 |
+
'contact_form' => array(
|
24 |
+
'screenshot' => App::publicUrl('img/forms/contact_form.png'),
|
25 |
+
'createable' => true,
|
26 |
+
'title' => 'Contact Form',
|
27 |
'brief' => 'A simple contact form for your site.',
|
28 |
+
'json' => '[{"id":"3","title":"Contact Form","form":{"fields":[{"index":2,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"First Name field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1516797564818"},{"index":7,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":"you@domain.com"},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"Email field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1516797583697"},{"index":2,"element":"textarea","attributes":{"name":"message","value":"","id":"","class":"","placeholder":"Type your message here...","rows":4,"cols":2},"settings":{"container_class":"","label":"Message","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"Message field is required"}},"conditional_logics":[]},"editor_options":{"title":"Textarea","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1516797589062"}],"submitButton":{"uniqElKey":"el_1524065200616","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline"}},"notifications":{"name":"Admin Notification Email","sendTo":{"type":"email","email":"{wp.admin_email}","field":"email","routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"[{inputs.names}] New Form Submission","message":"<p>{all_data}<\/p>\n<p>This form submitted at: {embed_post.permalink}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
29 |
),
|
30 |
+
'support_form' => array(
|
31 |
+
'screenshot' => App::publicUrl('img/forms/support_form.png'),
|
32 |
'createable' => true,
|
33 |
+
'title' => 'Support Form',
|
34 |
'brief' => 'Using this support form users can ask questions.',
|
35 |
+
'json' => '[{"id":"4","title":"Support Form","form":{"fields":[{"index":2,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"First Name field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1516797727681"},{"index":7,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":"you@domain.com"},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"Email field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1516797731343"},{"index":3,"element":"select","attributes":{"name":"department","value":"","id":"","class":""},"settings":{"label":"Department","help_message":"","container_class":"","label_placement":"","placeholder":"- Select Department -","validation_rules":{"required":{"value":true,"message":"Department field is required"}},"conditional_logics":[]},"options":{"Web Design":"Web Design","Web Development":"Web Development","WordPress Development":"WordPress Development","DevOps":"DevOps"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1516797735245"},{"index":1,"element":"input_text","attributes":{"type":"text","name":"subject","value":"","class":"","placeholder":"Type your subject"},"settings":{"container_class":"","label":"Subject","label_placement":"","admin_field_label":"","help_message":"","validation_rules":{"required":{"value":true,"message":"Subject field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Input","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1516797770161"},{"index":2,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Description","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Textarea","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1516797774136"}],"submitButton":{"uniqElKey":"el_1524065200616","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline"}},"notifications":{"name":"Support Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"{inputs.subject}, Support Form","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
36 |
),
|
37 |
+
'event_registration_form' => array(
|
38 |
+
'screenshot' => App::publicUrl('img/forms/registration_form.png'),
|
39 |
'createable' => true,
|
40 |
+
'title' => 'Event Registration Form',
|
41 |
'brief' => 'Using this registration form you can enable your event to achieve it\'s goal.',
|
42 |
+
'json' => '[{"id":"1","title":"Event Registration Form","form":{"fields":[{"index":2,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"First Name field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1516797727681"},{"index":7,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":"you@domain.com"},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"Email field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1516797731343"},{"index":14,"element":"input_number","attributes":{"type":"number","name":"phone","value":"","id":"","class":"","placeholder":"Your phone number"},"settings":{"container_class":"","label":"Phone","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"Phone field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1516798280530"},{"index":1,"element":"input_text","attributes":{"type":"text","name":"company","value":"","class":"","placeholder":"Type your company name"},"settings":{"container_class":"","label":"Company","label_placement":"","admin_field_label":"","help_message":"","validation_rules":{"required":{"value":false,"message":"Company field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Input","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1516797770161"},{"index":8,"element":"input_url","attributes":{"type":"url","name":"url","value":"","id":"","class":"","placeholder":"http:\/\/www.google.com"},"settings":{"container_class":"","label":"Website","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"Website field is required"},"url":{"value":true,"message":"This field must contain a valid url"}},"conditional_logics":[]},"editor_options":{"title":"URL","icon_class":"icon-link","template":"inputText"},"uniqElKey":"el_1516798321477"},{"index":2,"element":"textarea","attributes":{"name":"message","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Message (if any)","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Textarea","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1516797774136"}],"submitButton":{"uniqElKey":"el_1524065200616","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline"},"id":"29"},"notifications":{"name":"Event Registration Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Event Registration Form submitted","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
43 |
+
),
|
44 |
+
'vendor_contact_form' => array(
|
45 |
+
'screenshot' => App::publicUrl('img/forms/vendor_contact_orm.png'),
|
46 |
+
'createable' => true,
|
47 |
+
'title' => 'Vendor Contact Form',
|
48 |
+
'brief' => 'Easily create a vendor contact form and get looged in user details easily.',
|
49 |
+
'json' => '[{"id":"2","title":"Vendor Contact Form","form":{"fields":[{"index":0,"element":"input_hidden","attributes":{"type":"hidden","name":"hidden","value":"{user.first_name} {user.last_name}"},"settings":{"admin_field_label":"Seller Name"},"editor_options":{"title":"Hidden Field","icon_class":"icon-eye-slash","template":"inputHidden"},"uniqElKey":"el_1559107765827"},{"index":0,"element":"input_hidden","attributes":{"type":"hidden","name":"hidden_1","value":"{user.user_email}"},"settings":{"admin_field_label":"Seller mail"},"editor_options":{"title":"Hidden Field","icon_class":"icon-eye-slash","template":"inputHidden"},"uniqElKey":"el_1559106971216"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Subject","label_placement":"","admin_field_label":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559107049204"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Message","admin_field_label":"","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559107086831"}],"submitButton":{"uniqElKey":"el_1559106893440","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Vendor Contact Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"{inputs.input_text}","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
50 |
+
),
|
51 |
+
'patient_intake_form' => array(
|
52 |
+
'screenshot' => App::publicUrl('img/forms/patient_intake_form.png'),
|
53 |
+
'createable' => true,
|
54 |
+
'title' => 'Patient Intake Form',
|
55 |
+
'brief' => 'Using this form you can get the patient information with the medical history.',
|
56 |
+
'json' => '[{"id":"6","title":"Patient Intake Form","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Patient Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559109481426"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Patient Age","admin_field_label":"Patient Age","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559109564397"},{"index":0,"element":"input_name","attributes":{"name":"names_1","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Prefered Name \/ Nickname","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Prefered Name \/ Nickname","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559109792382"},{"index":7,"element":"select","attributes":{"name":"dropdown","value":"","id":"","class":""},"settings":{"label":"Patient Gender","admin_field_label":"","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Male":"Male","Female":"Female","Others":"Others"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559109642614"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Phone no.","admin_field_label":"Phone no.","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559274675098"},{"index":0,"element":"input_name","attributes":{"name":"names_2","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Spouce Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Spouce Name","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559109681450"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"With whome do you live?","label_placement":"","admin_field_label":"With whome do you live?","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274086054"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Marital Status","admin_field_label":"Marital Status","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Married":"Married","Unmarried":"Unmarried","other":"other"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559274134558"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_1","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Marital status(other)","label_placement":"","admin_field_label":"Marital status(other)","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio","value":"other","operator":"="}]}},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274183772"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_2","value":"","class":"","placeholder":"If retired or disabled enter your last occupation"},"settings":{"container_class":"","label":"Occupation","label_placement":"","admin_field_label":"Occupation","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274252192"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_1","value":""},"settings":{"container_class":"","label":"Retired? ","admin_field_label":"Retired? ","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559274467432"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Date of retirement","admin_field_label":"Date of retirement","label_placement":"","date_format":"m\/d\/y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio_1","value":"yes","operator":"="}]}},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559274499317"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_2","value":""},"settings":{"container_class":"","label":"Disability ?","admin_field_label":"Disability ?","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559274553377"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Date of disability","admin_field_label":"Date of disability","label_placement":"","date_format":"m\/d\/y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio_2","value":"yes","operator":"="}]}},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559274530031"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_3","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Who is your primary care doctor: ","label_placement":"","admin_field_label":"Who is your primary care doctor: ","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274621989"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_4","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Where is your primary care doctor located ? ","label_placement":"","admin_field_label":"Where is your primary care doctor located ? ","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274635900"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_5","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Phone Number of primary care doctor:","label_placement":"","admin_field_label":"Phone Number of primary care doctor:","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274697189"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_3","value":""},"settings":{"container_class":"","label":"allergic to any medications","admin_field_label":"allergic to any medications","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559274799405"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_6","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"allergic to any medications","label_placement":"","admin_field_label":"allergic to any medications","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio_3","value":"yes","operator":"="}]}},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274743792"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_4","value":""},"settings":{"container_class":"","label":"Do you smoke? ","admin_field_label":"Do you smoke? ","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559274844179"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_7","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"How many years did you smoke?","label_placement":"","admin_field_label":"How many years did you smoke?","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio_4","value":"yes","operator":"="}]}},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274869218"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_8","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"If you quit, when did you stop?","label_placement":"","admin_field_label":"If you quit, when did you stop?","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274922518"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_9","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Do you drink alcohol? ","label_placement":"","admin_field_label":"Do you drink alcohol? ","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559274938140"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Personal opinion","admin_field_label":"Personal opinion","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559275053975"}],"submitButton":{"uniqElKey":"el_1559109464078","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Patient Intake Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Patient Intake Form Notification","message":"<p>{all_data}<\/p>\n<p> <\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
57 |
+
),
|
58 |
+
'volunteer_application_form' => array(
|
59 |
+
'screenshot' => App::publicUrl('img/forms/volunteer_application.png'),
|
60 |
+
'createable' => true,
|
61 |
+
'title' => 'Volunteer Application Form',
|
62 |
+
'brief' => 'Get volunteer applicants data according to their particular interests with working days.',
|
63 |
+
'json' => '[{"id":"7","title":"Volunteer Application","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559110051637"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","admin_field_label":"Email","validation_rules":{"required":{"value":false,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559110054313"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Contact No","admin_field_label":"Contact No","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559110059986"},{"index":4,"element":"address","attributes":{"id":"","class":"","name":"address1","data-type":"address-element"},"settings":{"label":"Address","admin_field_label":"","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"address_line_1":{"element":"input_text","attributes":{"type":"text","name":"address_line_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Address Line 1","admin_field_label":"","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"address_line_2":{"element":"input_text","attributes":{"type":"text","name":"address_line_2","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Address Line 2","admin_field_label":"","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"city":{"element":"input_text","attributes":{"type":"text","name":"city","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"City","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"state":{"element":"input_text","attributes":{"type":"text","name":"state","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"State","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"zip":{"element":"input_text","attributes":{"type":"text","name":"zip","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Zip Code","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"country":{"element":"select_country","attributes":{"name":"country","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Country","admin_field_label":"","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"country_list":{"active_list":"all","visible_list":[],"hidden_list":[]},"conditional_logics":[]},"options":{"BD":"Bangladesh","US":"US of America"},"editor_options":{"title":"Country List","element":"country-list","icon_class":"icon-text-width","template":"selectCountry"}}},"editor_options":{"title":"Address Fields","element":"address-fields","icon_class":"icon-credit-card","template":"addressFields"},"uniqElKey":"el_1559110065995"},{"index":9,"element":"input_checkbox","attributes":{"type":"checkbox","name":"checkbox","value":[]},"settings":{"container_class":"","label":"Working Days","admin_field_label":"Working Days","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{" Sunday":" Sunday","Satarday":"Satarday","Monday":"Monday","Tuesday":"Tuesday","Wednesday":"Wednesday","Thursday":"Thursday","Friday":"Friday"},"editor_options":{"title":"Check Box","icon_class":"icon-check-square-o","template":"inputCheckbox"},"uniqElKey":"el_1559186288251"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Area of Interest\/ skills","admin_field_label":"Area of Interest\/ skills","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559186858825"},{"index":3,"element":"textarea","attributes":{"name":"description_1","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Comments (optional)","admin_field_label":"Comments (optional)","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559186958391"}],"submitButton":{"uniqElKey":"el_1559110013898","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Application","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Volunteer Application Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Volunteer Application by {inputs.names}","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
64 |
+
),
|
65 |
+
'request_for_quote' => array(
|
66 |
+
'screenshot' => App::publicUrl('img/forms/request_for_quote.png'),
|
67 |
+
'createable' => true,
|
68 |
+
'title' => 'Request for Quote',
|
69 |
+
'brief' => 'Get user request for quote using this simple form.',
|
70 |
+
'json' => '[{"id":"9","title":"Request for Quote","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559111679342"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Phone no.","admin_field_label":"Phone no.","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559111693607"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","admin_field_label":"Email","validation_rules":{"required":{"value":true,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559111716176"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Prefered Method of Contact","admin_field_label":"Prefered Method of Contact","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"","value":"","operator":""}]}},"options":{"Phone":"Phone","Email":"Email","other":"other"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559111739945"},{"index":4,"element":"address","attributes":{"id":"","class":"","name":"address1","data-type":"address-element"},"settings":{"label":"Address","admin_field_label":"","conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio","value":"other","operator":"="}]}},"fields":{"address_line_1":{"element":"input_text","attributes":{"type":"text","name":"address_line_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Address Line 1","admin_field_label":"","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"address_line_2":{"element":"input_text","attributes":{"type":"text","name":"address_line_2","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Address Line 2","admin_field_label":"","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"city":{"element":"input_text","attributes":{"type":"text","name":"city","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"City","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"state":{"element":"input_text","attributes":{"type":"text","name":"state","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"State","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"zip":{"element":"input_text","attributes":{"type":"text","name":"zip","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Zip Code","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"country":{"element":"select_country","attributes":{"name":"country","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Country","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"country_list":{"active_list":"all","visible_list":[],"hidden_list":[]},"conditional_logics":[]},"options":{"BD":"Bangladesh","US":"US of America"},"editor_options":{"title":"Country List","element":"country-list","icon_class":"icon-text-width","template":"selectCountry"}}},"editor_options":{"title":"Address Fields","element":"address-fields","icon_class":"icon-credit-card","template":"addressFields"},"uniqElKey":"el_1559111849017"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Comments","admin_field_label":"Comments","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559187328513"}],"submitButton":{"uniqElKey":"el_1559111649130","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Request for Quote Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Request for Quote","message":"<p>{all_data}<\/p>\n<p>Form Submitted successfully.<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
71 |
+
),
|
72 |
+
'conference_proposal' => array(
|
73 |
+
'screenshot' => App::publicUrl('img/forms/conference_proposal.png'),
|
74 |
+
'createable' => true,
|
75 |
+
'title' => 'Conference Proposal',
|
76 |
+
'brief' => 'This sample form is to make a conference proposal efficient and effective.',
|
77 |
+
'json' => '[{"id":"10","title":"Conference Proposal","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559112198786"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Job Title","label_placement":"","admin_field_label":"Job Title","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559112216398"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_1","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Company Name","label_placement":"","admin_field_label":"Company Name","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559112231309"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Biography","admin_field_label":"Biography","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559112255236"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","admin_field_label":"Email","validation_rules":{"required":{"value":true,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559112265631"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_2","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Proposal Title","label_placement":"","admin_field_label":"Proposal Title","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559112290259"},{"index":3,"element":"textarea","attributes":{"name":"description_1","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Short Description","admin_field_label":"Short Description","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559112314875"},{"index":3,"element":"textarea","attributes":{"name":"description_2","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Abstract","admin_field_label":"Abstract","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559112331026"},{"index":9,"element":"input_checkbox","attributes":{"type":"checkbox","name":"checkbox","value":[]},"settings":{"container_class":"","label":"Topics","admin_field_label":"Topics","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"Topics 1":"Topics 1","Topics 2":"Topics 2","Topics 3":"Topics 3"},"editor_options":{"title":"Check Box","icon_class":"icon-check-square-o","template":"inputCheckbox"},"uniqElKey":"el_1559112363613"},{"index":7,"element":"select","attributes":{"name":"dropdown","value":"","id":"","class":""},"settings":{"label":"Session Type","admin_field_label":"","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Panel":"Panel","Work shop":"Work shop","Presentation":"Presentation","Other":"Other"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559112402526"},{"index":7,"element":"select","attributes":{"name":"dropdown_1","value":"","id":"","class":""},"settings":{"label":"Audience Level","admin_field_label":"Audience Level","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Novice":"Novice","Intermediate":"Intermediate","Expert":"Expert"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559112480834"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_3","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Video URL","label_placement":"","admin_field_label":"Video URL","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559112543915"},{"index":3,"element":"textarea","attributes":{"name":"description_3","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Additional Information","admin_field_label":"Additional Information","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559112578458"}],"submitButton":{"uniqElKey":"el_1559112181623","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"center","button_style":"green","container_class":"","help_message":"","background_color":"#67C23A","button_size":"lg","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form Data","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Conference Proposal Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Conference Proposal","message":"<p>Your data submitted Successfully<\/p>\n<p>{all_data}<\/p>\n<p> <\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
78 |
+
),
|
79 |
+
'report_a_bug' => array(
|
80 |
+
'screenshot' => App::publicUrl('img/forms/report_a_bug.png'),
|
81 |
+
'createable' => true,
|
82 |
+
'title' => 'Report a bug',
|
83 |
+
'brief' => 'You can get user feedback, bug repport, and the issue details from user.',
|
84 |
+
'json' => '[{"id":"11","title":"Report a bug","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Full Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Full Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559112858212"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Enter Your Email Address ","label_placement":"","help_message":"","admin_field_label":"Email Address","validation_rules":{"required":{"value":true,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559112923651"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Title of the Issue","label_placement":"","admin_field_label":"Title of the Issue","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559112936885"},{"index":7,"element":"select","attributes":{"name":"dropdown","value":"","id":"","class":""},"settings":{"label":"Problem Status","admin_field_label":"Problem Status","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Open":"Open","Hold":"Hold","Fixed":"Fixed","Closed":"Closed","Invalid":"Invalid","Other":"Other"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559112981010"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Summary of the Information","admin_field_label":"Summary Information","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559113034559"},{"index":3,"element":"textarea","attributes":{"name":"description_1","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Steps to Reproduce","admin_field_label":"Steps to Reproduce","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559113051369"},{"index":3,"element":"textarea","attributes":{"name":"description_2","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Results","admin_field_label":"Results","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559113074493"},{"index":3,"element":"textarea","attributes":{"name":"description_3","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Regression","admin_field_label":"Regression","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559113093300"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Is there a Workaround?","admin_field_label":"","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559113134071"},{"index":3,"element":"textarea","attributes":{"name":"description_4","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Documentation & Notes","admin_field_label":"Documentation & Notes","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559113248045"},{"index":1,"element":"container","attributes":[],"settings":[],"columns":[{"fields":[{"index":3,"element":"custom_html","attributes":[],"settings":{"html_codes":"<p style=\"text-align: right\">Upload Your documents (if need)<\/p>","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"editor_options":{"title":"Custom HTML","icon_class":"icon-code","template":"customHTML"},"uniqElKey":"el_1559187803664"}]},{"fields":[{"index":14,"element":"input_file","attributes":{"type":"file","name":"file-upload","value":"","id":"","class":""},"settings":{"container_class":"","label":"","admin_field_label":"Optional documents","label_placement":"right","btn_text":"Choose File","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"},"max_file_size":{"value":2097152,"_valueFrom":"MB","message":"Maximum file size limit"},"max_file_count":{"value":1,"message":"Max file count"},"allowed_file_types":{"value":["jpg|jpeg|gif|png|bmp","mp3|wav|ogg|wma|mka|m4a|ra|mid|midi","avi|divx|flv|mov|ogv|mkv|mp4|m4v|divx|mpg|mpeg|mpe|video\/quicktime|qt","pdf","doc|ppt|pps|xls|mdb|docx|xlsx|pptx|odt|odp|ods|odg|odc|odb|odf|rtf|txt","zip|gz|gzip|rar|7z","exe","csv"],"message":"allowed_file_types"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"editor_options":{"title":"File Upload","icon_class":"icon-upload","template":"inputFile"},"uniqElKey":"el_1559187757610"}]}],"editor_options":{"title":"Two Column Container","icon_class":"icon-columns"},"uniqElKey":"el_1559187753426"},{"index":7,"element":"select","attributes":{"name":"dropdown_1","value":"","id":"","class":""},"settings":{"label":"Reproducibility","admin_field_label":"Reproducibility","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"I didn\'t try":"I didn\'t try","Rarely":"Rarely","Sometimes":"Sometimes","Always":"Always"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559113160537"},{"index":7,"element":"select","attributes":{"name":"dropdown_3","value":"","id":"","class":""},"settings":{"label":"Classification of Bug","admin_field_label":"Classification of Bug","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Security":"Security","Crash \/Hang \/Data loss":"Crash \/Hang \/Data loss","Performance \/Ui-Usability":"Performance \/Ui-Usability","Serious Bug":"Serious Bug","Other Bug":"Other Bug","Feature (New)":"Feature (New)","Enhancement":"Enhancement"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559113361150"},{"index":7,"element":"select","attributes":{"name":"dropdown_2","value":"","id":"","class":""},"settings":{"label":"How severe it is?","admin_field_label":"How severe it is?","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Trivial":"Trivial","Normal":"Normal","Major":"Major","Critical":"Critical"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559113271926"}],"submitButton":{"uniqElKey":"el_1559112849800","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"orange","container_class":"","help_message":"","background_color":"#E6A23C","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Bug Report","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Bug Report Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"New Bug Reported","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
85 |
+
),
|
86 |
+
'polling_form' => array(
|
87 |
+
'screenshot' => App::publicUrl('img/forms/polling_form.png'),
|
88 |
+
'createable' => true,
|
89 |
+
'title' => 'Polling Form',
|
90 |
+
'brief' => 'A sample polling form to get user opinion from your scheduled time.',
|
91 |
+
'json' => '[{"id":"14","title":"Polling Form","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Full Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Full Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559116201333"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","admin_field_label":"Email","validation_rules":{"required":{"value":true,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559116203187"},{"index":9,"element":"input_checkbox","attributes":{"type":"checkbox","name":"checkbox","value":[]},"settings":{"container_class":"","label":"Which game you want to play?","admin_field_label":"Which game you want to play?","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Football":"Football","Cricket":"Cricket","Hocky":"Hocky"},"editor_options":{"title":"Check Box","icon_class":"icon-check-square-o","template":"inputCheckbox"},"uniqElKey":"el_1559116572343"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_1","value":""},"settings":{"container_class":"","label":"Time of the match?","admin_field_label":"Time of the match?","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Morning":"Morning","Afternoon":"Afternoon","Any time":"Any time"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559116394587"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Put your suggestion (optional)","admin_field_label":"suggestions","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559188488518"}],"submitButton":{"uniqElKey":"el_1559116097684","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"green","container_class":"","help_message":"","background_color":"#67C23A","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Your opinion","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Polling Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Polling Form","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
92 |
+
),
|
93 |
+
'tell_a_friend_form' => array(
|
94 |
+
'screenshot' => App::publicUrl('img/forms/tell_a_friend_form.png'),
|
95 |
+
'createable' => true,
|
96 |
+
'title' => 'Tell A Friend Form',
|
97 |
+
'brief' => 'Get text from you site users.',
|
98 |
+
'json' => '[{"id":"15","title":"Tell A Friend Form","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559117552587"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"To","label_placement":"","admin_field_label":"To","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559117572857"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Message","admin_field_label":"Message","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559117587626"}],"submitButton":{"uniqElKey":"el_1559117545661","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"center","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Send Message","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Tell A Friend Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Text submitted","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
99 |
+
),
|
100 |
+
'directory_information_form' => array(
|
101 |
+
'screenshot' => App::publicUrl('img/forms/my_directory_information.png'),
|
102 |
+
'createable' => true,
|
103 |
+
'title' => 'Directory Information Form',
|
104 |
+
'brief' => 'This form allows you to get the directory informations.',
|
105 |
+
'json' => '[{"id":"16","title":"My Directory Information","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559117767222"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Username","label_placement":"","admin_field_label":"Username","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559117793585"},{"index":11,"element":"input_url","attributes":{"type":"url","name":"url","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Site Address","admin_field_label":"Address","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"url":{"value":true,"message":"This field must contain a valid url"}},"conditional_logics":[]},"editor_options":{"title":"Website URL","icon_class":"icon-link","template":"inputText"},"uniqElKey":"el_1559117830376"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":"no"},"settings":{"container_class":"","label":"Do you want to share your password?","admin_field_label":"Agree to share password","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559188732753"},{"index":12,"element":"input_password","attributes":{"type":"password","name":"password","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Enter your password","admin_field_label":"password","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio","value":"yes","operator":"="}]}},"editor_options":{"title":"Password Field","icon_class":"icon-lock","template":"inputText"},"uniqElKey":"el_1559188774833"}],"submitButton":{"uniqElKey":"el_1559117761747","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Informations","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Directory Information Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Directory Information submited","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
106 |
+
),
|
107 |
+
'request_for_leave' => array(
|
108 |
+
'screenshot' => App::publicUrl('img/forms/request_for_leave.png'),
|
109 |
+
'createable' => true,
|
110 |
+
'title' => 'Request for Leave',
|
111 |
+
'brief' => 'Receive leave requests from your employee instantly.',
|
112 |
+
'json' => '[{"id":"18","title":"Request for Leave","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559119229242"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email address(optional)","label_placement":"","help_message":"","admin_field_label":"Email address","validation_rules":{"required":{"value":false,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559189778769"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Contact no.","admin_field_label":"Contact no.","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559275288135"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Position","label_placement":"","admin_field_label":"Position","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559119248104"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_1","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Manager","label_placement":"","admin_field_label":"Manager","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559119277903"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Leave Start ","admin_field_label":"Leave Start ","label_placement":"","date_format":"d\/m\/Y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559119287246"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Leave End","admin_field_label":"Leave End","label_placement":"","date_format":"d\/m\/Y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559119907871"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Leave Type ","admin_field_label":"Leave Type ","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Vacation":"Vacation","Sick":"Sick","Quitting":"Quitting","Maternity\/Paternity":"Maternity\/Paternity","Other":"Other"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559119926213"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Comments","admin_field_label":"Comments","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559119977368"}],"submitButton":{"uniqElKey":"el_1559119222045","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Apply For Leave","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Request for Leave Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"New Request for Leave","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
113 |
+
),
|
114 |
+
'admission_form' => array(
|
115 |
+
'screenshot' => App::publicUrl('img/forms/admissions_form.png'),
|
116 |
+
'createable' => true,
|
117 |
+
'is_pro' => true,
|
118 |
+
'title' => 'Admissions Form (Pro)',
|
119 |
+
'brief' => 'Receive new admission students data along with their documents using this sample admission form.',
|
120 |
+
'json' => '[{"id":"5","title":"Admissions Form (Pro)","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559108914252"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","admin_field_label":"Email","validation_rules":{"required":{"value":false,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559116838095"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Birth Date","admin_field_label":"","label_placement":"","date_format":"n\/j\/y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559108940886"},{"index":9,"element":"input_checkbox","attributes":{"type":"checkbox","name":"checkbox","value":[]},"settings":{"container_class":"","label":"Gender","admin_field_label":"","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"options":{"Male":"Male","Female":"Female","Others":"Others"},"editor_options":{"title":"Check Box","icon_class":"icon-check-square-o","template":"inputCheckbox"},"uniqElKey":"el_1559109018303"},{"index":7,"element":"select","attributes":{"name":"dropdown","value":"","id":"","class":""},"settings":{"label":"Select section","admin_field_label":"","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Section - A":"Section - A","Section - B":"Section - B","Section - C":"Section - C"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559109122333"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Apply for Class","admin_field_label":"Apply for Class","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"O level":"O level","A level":"A level"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559116799977"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Guardian Name","label_placement":"","admin_field_label":"Guardian Name","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559116859101"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Guardian Contact no.","admin_field_label":"Guardian Contact no.","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559116996267"},{"index":15,"element":"input_image","attributes":{"type":"file","name":"image-upload","value":"","id":"","class":"","accept":"image\/*"},"settings":{"container_class":"","label":"Upload Your photo","admin_field_label":"","label_placement":"left","btn_text":"Choose File","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"max_file_size":{"value":1048576,"_valueFrom":"MB","message":"Maximum file size limit"},"max_file_count":{"value":1,"message":"Max file count"},"allowed_image_types":{"value":["jpg|jpeg","png"],"message":"Allowed image size does not match"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"editor_options":{"title":"Image Upload","icon_class":"icon-picture-o","template":"inputFile"},"uniqElKey":"el_1559117075053"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Comments (optional)","admin_field_label":"Comments(optional)","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559275440101"}],"submitButton":{"uniqElKey":"el_1559108903621","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"right","button_style":"green","container_class":"","help_message":"","background_color":"#67C23A","button_size":"lg","color":"#ffffff","button_ui":{"type":"default","text":"Apply for Admission","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Admissions Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"New Admissions Form submited","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
121 |
+
),
|
122 |
+
'loan_application_form' => array(
|
123 |
+
'screenshot' => App::publicUrl('img/forms/loan_application.png'),
|
124 |
+
'createable' => true,
|
125 |
+
'title' => 'Loan Application Form (Pro)',
|
126 |
+
'is_pro' => true,
|
127 |
+
'brief' => 'Get loan application from applicants with their detail information.',
|
128 |
+
'json' => '[{"id":"8","title":"Loan Application (Pro)","form":{"fields":[{"index":1,"element":"section_break","attributes":{"id":"","class":""},"settings":{"label":"Personal Information","description":"","align":"left","conditional_logics":[]},"editor_options":{"title":"Section Break","icon_class":"icon-puzzle-piece","template":"sectionBreak"},"uniqElKey":"el_1559207413587"},{"index":7,"element":"select","attributes":{"name":"dropdown","value":"","id":"","class":""},"settings":{"label":"Title","admin_field_label":"Title","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Mr":"Mr","Mrs":"Mrs","Ms":"Ms"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559110533621"},{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559110355217"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Birth Date","admin_field_label":"Birth Date","label_placement":"","date_format":"d.m.Y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559110597665"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Marital Status","admin_field_label":"Marital Status","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Single":"Single","Married":"Married","Other":"Other"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559110647827"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email","label_placement":"","help_message":"","admin_field_label":"Applicant Email","validation_rules":{"required":{"value":true,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559110700355"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Phone no.","admin_field_label":"Phone no.","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559110739318"},{"index":4,"element":"address","attributes":{"id":"","class":"","name":"address1","data-type":"address-element"},"settings":{"label":"Address","admin_field_label":"","conditional_logics":[]},"fields":{"address_line_1":{"element":"input_text","attributes":{"type":"text","name":"address_line_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Address Line 1","admin_field_label":"","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"address_line_2":{"element":"input_text","attributes":{"type":"text","name":"address_line_2","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Address Line 2","admin_field_label":"","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"city":{"element":"input_text","attributes":{"type":"text","name":"city","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"City","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"state":{"element":"input_text","attributes":{"type":"text","name":"state","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"State","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"zip":{"element":"input_text","attributes":{"type":"text","name":"zip","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Zip Code","admin_field_label":"","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"country":{"element":"select_country","attributes":{"name":"country","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Country","admin_field_label":"","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"country_list":{"active_list":"all","visible_list":[],"hidden_list":[]},"conditional_logics":[]},"options":{"BD":"Bangladesh","US":"US of America"},"editor_options":{"title":"Country List","element":"country-list","icon_class":"icon-text-width","template":"selectCountry"}}},"editor_options":{"title":"Address Fields","element":"address-fields","icon_class":"icon-credit-card","template":"addressFields"},"uniqElKey":"el_1559110782924"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_1","value":""},"settings":{"container_class":"","label":"How long have you lived in your given address? ","admin_field_label":"Lived in this address (years)","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"0-1 years":"0-1 years","1-2 years":"1-2 years","2-3 years":"2-3 years","3-4 years":"3-4 years","5+ years":"5+ years"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559110849005"},{"index":7,"element":"form_step","attributes":{"id":"","class":""},"settings":{"prev_btn":{"type":"default","text":"Previous","img_url":""},"next_btn":{"type":"default","text":"Next","img_url":""}},"editor_options":{"title":"Form Step","icon_class":"icon-step-forward","template":"formStep"},"uniqElKey":"el_1559207478467"},{"index":1,"element":"section_break","attributes":{"id":"","class":""},"settings":{"label":"Employment Information","description":"Put Your Employment Information bellow,\nPlease fill all the required.","align":"center","conditional_logics":[]},"editor_options":{"title":"Section Break","icon_class":"icon-puzzle-piece","template":"sectionBreak"},"uniqElKey":"el_1559110949366"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Present Employer ","label_placement":"","admin_field_label":"Present Employer ","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559111023883"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_1","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Occupation","label_placement":"","admin_field_label":"Occupation","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559111049075"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio_2","value":""},"settings":{"container_class":"","label":"Experience of work (years)","admin_field_label":"Experience of work (years)","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"0-1 years":"0-1 years","1-2 years":"1-2 years","2-3 years":"2-3 years","3-4 years":"3-4 years","5+ years":"5+ years"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559111110100"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field_1","value":"","id":"","class":"","placeholder":"Ex 15000"},"settings":{"container_class":"","label":"Gross monthly income ","admin_field_label":"Gross monthly income ","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559111169812"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field_2","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Monthly rent\/mortgage","admin_field_label":"Monthly rent\/mortgage","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559111225701"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field_3","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Down Payment Amount","admin_field_label":"Down Payment Amount","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":[]},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559111277768"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"Put your comments here (optional)","rows":4,"cols":2},"settings":{"container_class":"","label":"Comments","admin_field_label":"Comments","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559111302333"},{"index":5,"element":"terms_and_condition","attributes":{"type":"checkbox","name":"terms-n-condition","value":false,"class":""},"settings":{"tnc_html":"I have read and agree to the <a href=\"#\">Terms and Conditions<\/a> and <a href=\"#\">Privacy Policy<\/a>","has_checkbox":true,"admin_field_label":"Terms and Conditions","container_class":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Terms & Conditions","icon_class":"icon-check-square-o","template":"termsCheckbox"},"uniqElKey":"el_1559111347132"}],"submitButton":{"uniqElKey":"el_1559110346631","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}},"stepsWrapper":{"stepStart":{"element":"step_start","attributes":{"id":"","class":""},"settings":{"progress_indicator":"","step_titles":[]},"editor_options":{"title":"Start Paging"}},"stepEnd":{"element":"step_end","attributes":{"id":"","class":""},"settings":{"prev_btn":{"type":"default","text":"Previous","img_url":""}},"editor_options":{"title":"End Paging"}}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Loan Application Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Loan Application Form submited","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
129 |
+
),
|
130 |
+
'job_listing_form' => array(
|
131 |
+
'screenshot' => App::publicUrl('img/forms/job_listing.png'),
|
132 |
+
'createable' => true,
|
133 |
+
'title' => 'Job Listing Form (Pro)',
|
134 |
+
'is_pro' => true,
|
135 |
+
'brief' => 'Get application and uploaded resume from online applicants.',
|
136 |
+
'json' => '[{"id":"17","title":"Job Listing Form (Pro)","form":{"fields":[{"index":1,"element":"section_break","attributes":{"id":"","class":""},"settings":{"label":"Personal informations","description":"","align":"left","conditional_logics":[]},"editor_options":{"title":"Section Break","icon_class":"icon-puzzle-piece","template":"sectionBreak"},"uniqElKey":"el_1559205807740"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Company Name","label_placement":"","admin_field_label":"Company Name","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559118283900"},{"index":7,"element":"select","attributes":{"name":"dropdown","value":"","id":"","class":""},"settings":{"label":"Salutation","admin_field_label":"Salutation","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"Mr":"Mr","Mrs":"Mrs","Miss":"Miss","Ms.":"Ms.","Dr.":"Dr.","Prof.":"Prof."},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559118312135"},{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"First Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559118403552"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email address","label_placement":"","help_message":"","admin_field_label":"Email address","validation_rules":{"required":{"value":false,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559205920436"},{"index":5,"element":"terms_and_condition","attributes":{"type":"checkbox","name":"terms-n-condition","value":false,"class":""},"settings":{"tnc_html":"Agree to show contact information in public posting (optional)","has_checkbox":true,"admin_field_label":"Terms and Conditions","container_class":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Terms & Conditions","icon_class":"icon-check-square-o","template":"termsCheckbox"},"uniqElKey":"el_1559118426293"},{"index":7,"element":"form_step","attributes":{"id":"","class":""},"settings":{"prev_btn":{"type":"default","text":"Previous","img_url":""},"next_btn":{"type":"default","text":"Next","img_url":""}},"editor_options":{"title":"Form Step","icon_class":"icon-step-forward","template":"formStep"},"uniqElKey":"el_1559189413198"},{"index":1,"element":"section_break","attributes":{"id":"","class":""},"settings":{"label":"Information of your Position ","description":"","align":"left","conditional_logics":[]},"editor_options":{"title":"Section Break","icon_class":"icon-puzzle-piece","template":"sectionBreak"},"uniqElKey":"el_1559118465186"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_1","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Company Name","label_placement":"","admin_field_label":"Company Name","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559118495931"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_2","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Department \/ Division","label_placement":"","admin_field_label":"Department \/ Division","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559118508878"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_3","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Position Title","label_placement":"","admin_field_label":"Position Title","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559118527032"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_4","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Reference Number","label_placement":"","admin_field_label":"Reference Number","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559118539776"},{"index":11,"element":"input_url","attributes":{"type":"url","name":"url","value":"","class":"","placeholder":"ex. http:\/\/www.example.com\/"},"settings":{"container_class":"","label":"Job Posting Url","admin_field_label":"Job Posting Url","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"},"url":{"value":true,"message":"This field must contain a valid url"}},"conditional_logics":[]},"editor_options":{"title":"Website URL","icon_class":"icon-link","template":"inputText"},"uniqElKey":"el_1559118552911"},{"index":2,"element":"input_text","attributes":{"type":"text","name":"input_text_5","value":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Salary Details","label_placement":"","admin_field_label":"Salary Details","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Simple Text","icon_class":"icon-text-width","template":"inputText"},"uniqElKey":"el_1559118570345"},{"index":5,"element":"terms_and_condition","attributes":{"type":"checkbox","name":"terms-n-condition_1","value":false,"class":""},"settings":{"tnc_html":"Salary Negotiable","has_checkbox":true,"admin_field_label":"Salary Negotiable","container_class":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"editor_options":{"title":"Terms & Conditions","icon_class":"icon-check-square-o","template":"termsCheckbox"},"uniqElKey":"el_1559118595891"},{"index":7,"element":"select","attributes":{"name":"dropdown_1","value":"","id":"","class":""},"settings":{"label":"Type of Employment","admin_field_label":"Type of Employment","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"Full time":"Full time","Part time":"Part time"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559118624957"},{"index":7,"element":"select","attributes":{"name":"dropdown_2","value":"","id":"","class":""},"settings":{"label":"Type of Contract","admin_field_label":"","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"permanent":"permanent","Term\/Contract":"Term\/Contract","Locum":"Locum"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559118667887"},{"index":7,"element":"select","attributes":{"name":"dropdown_3","value":"","id":"","class":""},"settings":{"label":"Type Of the Position","admin_field_label":"Type Of the Position","help_message":"","container_class":"","label_placement":"","placeholder":"- Select -","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{"Academic":"Academic","Administrative":"Administrative","Obstetrics":"Obstetrics","Gynaecology":"Gynaecology","Obstetrics\/Gynaecology":"Obstetrics\/Gynaecology","Other":"Other"},"editor_options":{"title":"Dropdown","icon_class":"icon-caret-square-o-down","element":"select","template":"select"},"uniqElKey":"el_1559118729498"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Job Summary","admin_field_label":"Job Summary","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559118826214"},{"index":3,"element":"textarea","attributes":{"name":"description_1","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Roles and Responsibilities","admin_field_label":"Roles and Responsibilities","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559118841680"},{"index":3,"element":"textarea","attributes":{"name":"description_2","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Skills and Competencies","admin_field_label":"Skills and Competencies","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559118857624"},{"index":3,"element":"textarea","attributes":{"name":"description_3","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Education and Experience","admin_field_label":"Education and Experience","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559118873772"},{"index":3,"element":"textarea","attributes":{"name":"description_4","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Other","admin_field_label":"Other","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559118895131"},{"index":14,"element":"input_file","attributes":{"type":"file","name":"file-upload","value":"","id":"","class":""},"settings":{"container_class":"","label":"Upload your Cv","admin_field_label":"CV","label_placement":"right","btn_text":"Choose File","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"},"max_file_size":{"value":5242880,"_valueFrom":"MB","message":"Maximum file size limit"},"max_file_count":{"value":"-1","message":"Max file count"},"allowed_file_types":{"value":["pdf"],"message":"allowed_file_types"}},"conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"editor_options":{"title":"File Upload","icon_class":"icon-upload","template":"inputFile"},"uniqElKey":"el_1559282340628"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Start Date of Employment","admin_field_label":"Start Date of Employment","label_placement":"","date_format":"M\/d\/Y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559118920295"},{"index":13,"element":"input_date","attributes":{"type":"text","name":"datetime_1","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Application Deadline","admin_field_label":"Application Deadline","label_placement":"","date_format":"M\/d\/Y","help_message":"","is_time_enabled":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Time & Date","icon_class":"icon-calendar-o","template":"inputText"},"uniqElKey":"el_1559118959353"},{"index":7,"element":"form_step","attributes":{"id":"","class":""},"settings":{"prev_btn":{"type":"default","text":"Previous","img_url":""},"next_btn":{"type":"default","text":"Next","img_url":""}},"editor_options":{"title":"Form Step","icon_class":"icon-step-forward","template":"formStep"},"uniqElKey":"el_1559189453510"},{"index":1,"element":"section_break","attributes":{"id":"","class":""},"settings":{"label":"Payment Information","description":"","align":"left","conditional_logics":[]},"editor_options":{"title":"Section Break","icon_class":"icon-puzzle-piece","template":"sectionBreak"},"uniqElKey":"el_1559118987496"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Salary Amount","admin_field_label":"Salary Amount","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"options":{" $ 400.00 - 1 Month Posting Duration":" $ 400.00 - 1 Month Posting Duration","$ 600.00 - 2 Month Posting Duration":"$ 600.00 - 2 Month Posting Duration"," $ 900.00 - 3 Month Posting Duration":" $ 900.00 - 3 Month Posting Duration","other":"other"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559119006582"},{"index":6,"element":"input_number","attributes":{"type":"number","name":"numeric-field","value":"","id":"","class":"","placeholder":"Ex. 1000 "},"settings":{"container_class":"","label":"salary amount ($)","admin_field_label":"salary amount","label_placement":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"},"numeric":{"value":true,"message":"This field must contain numeric value"},"min":{"value":"","message":"Minimum value is "},"max":{"value":"","message":"Maximum value is "}},"conditional_logics":{"type":"any","status":true,"conditions":[{"field":"input_radio","value":"other","operator":"="}]}},"editor_options":{"title":"Numeric Field","icon_class":"icon-slack","template":"inputText"},"uniqElKey":"el_1559189169554"}],"submitButton":{"uniqElKey":"el_1559118268163","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Submit Form","img_url":""}},"editor_options":{"title":"Submit Button"}},"stepsWrapper":{"stepStart":{"element":"step_start","attributes":{"id":"","class":""},"settings":{"progress_indicator":"","step_titles":[]},"editor_options":{"title":"Start Paging"}},"stepEnd":{"element":"step_end","attributes":{"id":"","class":""},"settings":{"prev_btn":{"type":"default","text":"Previous","img_url":""}},"editor_options":{"title":"End Paging"}}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Job Listing Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Job Listing Form submission","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
137 |
+
),
|
138 |
+
'website_feedback' => array(
|
139 |
+
'screenshot' => App::publicUrl('img/forms/website_feedback.png'),
|
140 |
+
'createable' => true,
|
141 |
+
'title' => 'Website Feedback (Pro)',
|
142 |
+
'is_pro' => true,
|
143 |
+
'brief' => 'To make effective for your site users, you may get the feedback and suggestions using this sample form.',
|
144 |
+
'json' => '[{"id":"12","title":"Website Feedback (Pro)","form":{"fields":[{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Full Name","conditional_logics":{"type":"any","status":false,"conditions":[{"field":"","value":"","operator":""}]}},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Enter Your Full Name","help_message":"","visible":true,"validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559114141631"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Your Email address","label_placement":"","help_message":"","admin_field_label":"Email","validation_rules":{"required":{"value":true,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559114139212"},{"index":8,"element":"input_radio","attributes":{"type":"radio","name":"input_radio","value":""},"settings":{"container_class":"","label":"Is this the first time you have visited the website?","admin_field_label":"Is this the first time you have visited the website?","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"yes":"Yes","no":"No"},"editor_options":{"title":"Radio Button","icon_class":"icon-dot-circle-o","element":"input-radio","template":"inputRadio"},"uniqElKey":"el_1559114154264"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"What is the PRIMARY reason you came to the site?","admin_field_label":"What is the PRIMARY reason you came to the site?","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559114186791"},{"index":9,"element":"input_checkbox","attributes":{"type":"checkbox","name":"checkbox","value":[]},"settings":{"container_class":"","label":"Did you find what you needed?","admin_field_label":"Did you find what you needed?","label_placement":"","display_type":"","help_message":"","validation_rules":{"required":{"value":true,"message":"This field is required"}},"conditional_logics":[]},"options":{"Yes, all of it":"Yes, all of it","Yes, some of it":"Yes, some of it","No, none of it":"No, none of it"},"editor_options":{"title":"Check Box","icon_class":"icon-check-square-o","template":"inputCheckbox"},"uniqElKey":"el_1559114208705"},{"index":8,"element":"ratings","attributes":{"class":"","value":0,"name":"ratings"},"settings":{"label":"User Friendlyness","show_text":false,"help_message":"","label_placement":"","admin_field_label":"User Friendlyness","container_class":"","conditional_logics":[],"validation_rules":{"required":{"value":true,"message":"This field is required"}}},"options":{"1":"Nice","2":"Good","3":"Very Good","4":"Awesome","5":"Amazing"},"editor_options":{"title":"Ratings","icon_class":"icon-eye-slash","template":"ratings"},"uniqElKey":"el_1559114515484"}],"submitButton":{"uniqElKey":"el_1559114078404","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"default","container_class":"","help_message":"","background_color":"#409EFF","button_size":"md","color":"#ffffff","button_ui":{"type":"default","text":"Send your feedback","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":" Website Feedback Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Website Feedback","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
145 |
+
),
|
146 |
+
'comment_rating_form' => array(
|
147 |
+
'screenshot' => App::publicUrl('img/forms/comment_rating.png'),
|
148 |
+
'createable' => true,
|
149 |
+
'title' => 'Comment & Rating Form (Pro)',
|
150 |
+
'is_pro' => true,
|
151 |
+
'brief' => 'Get user ratings and feedback instantly.',
|
152 |
+
'json' => '[{"id":"13","title":"Comment & Rating (Pro)","form":{"fields":[{"index":8,"element":"ratings","attributes":{"class":"","value":0,"name":"ratings"},"settings":{"label":"Your Ratings","show_text":false,"help_message":"","label_placement":"","admin_field_label":"","container_class":"","conditional_logics":[],"validation_rules":{"required":{"value":true,"message":"This field is required"}}},"options":{"1":"Nice","2":"Good","3":"Very Good","4":"Awesome","5":"Amazing"},"editor_options":{"title":"Ratings","icon_class":"icon-eye-slash","template":"ratings"},"uniqElKey":"el_1559114708603"},{"index":0,"element":"input_name","attributes":{"name":"names","data-type":"name-element"},"settings":{"container_class":"","admin_field_label":"Name","conditional_logics":[]},"fields":{"first_name":{"element":"input_text","attributes":{"type":"text","name":"first_name","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Your Name (optional)","help_message":"","visible":true,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"middle_name":{"element":"input_text","attributes":{"type":"text","name":"middle_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Middle Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}},"last_name":{"element":"input_text","attributes":{"type":"text","name":"last_name","value":"","id":"","class":"","placeholder":"","required":false},"settings":{"container_class":"","label":"Last Name","help_message":"","error_message":"","visible":false,"validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"template":"inputText"}}},"editor_options":{"title":"Name Fields","element":"name-fields","icon_class":"icon-user","template":"nameFields"},"uniqElKey":"el_1559114691660"},{"index":1,"element":"input_email","attributes":{"type":"email","name":"email","value":"","id":"","class":"","placeholder":""},"settings":{"container_class":"","label":"Email address (optional)","label_placement":"","help_message":"","admin_field_label":"","validation_rules":{"required":{"value":false,"message":"This field is required"},"email":{"value":true,"message":"This field must contain a valid email"}},"conditional_logics":[]},"editor_options":{"title":"Email Address","icon_class":"icon-envelope-o","template":"inputText"},"uniqElKey":"el_1559188418772"},{"index":3,"element":"textarea","attributes":{"name":"description","value":"","id":"","class":"","placeholder":"","rows":4,"cols":2},"settings":{"container_class":"","label":"Comments","admin_field_label":"Comments","label_placement":"","help_message":"","validation_rules":{"required":{"value":false,"message":"This field is required"}},"conditional_logics":[]},"editor_options":{"title":"Text Area","icon_class":"icon-paragraph","template":"inputTextarea"},"uniqElKey":"el_1559114740479"}],"submitButton":{"uniqElKey":"el_1559114683138","element":"button","attributes":{"type":"submit","class":""},"settings":{"align":"left","button_style":"green","container_class":"","help_message":"","background_color":"#67C23A","button_size":"lg","color":"#ffffff","button_ui":{"type":"default","text":"Submit Ratings","img_url":""}},"editor_options":{"title":"Submit Button"}}},"formSettings":{"confirmation":{"redirectTo":"samePage","messageToShow":"Thank you for your message. We will get in touch with you shortly","customPage":null,"samePageFormBehavior":"hide_form","customUrl":null},"restrictions":{"limitNumberOfEntries":{"enabled":false,"numberOfEntries":null,"period":"total","limitReachedMsg":null},"scheduleForm":{"enabled":false,"start":null,"end":null,"pendingMsg":"Form submission is not started yet.","expiredMsg":"Form submission is now closed."},"requireLogin":{"enabled":false,"requireLoginMsg":null},"denyEmptySubmission":{"enabled":false,"message":"Sorry, you cannot submit an empty form. Let\'s hear what you wanna say."}},"layout":{"labelPlacement":"top","helpMessagePlacement":"with_label","errorMessagePlacement":"inline","cssClassName":""}},"notifications":{"name":"Comment & Rating Form Notification","sendTo":{"type":"email","email":"{wp.admin_email}","field":null,"routing":[{"email":null,"field":null,"operator":"=","value":null}]},"fromName":"","fromEmail":"","replyTo":"","bcc":"","subject":"Comment & Rating ","message":"<p>{all_data}<\/p>","conditionals":{"status":false,"type":"all","conditions":[{"field":null,"operator":"=","value":null}]},"enabled":false,"email_template":""}}]'
|
153 |
+
),
|
154 |
);
|
155 |
}
|
156 |
|
159 |
*/
|
160 |
public function all()
|
161 |
{
|
162 |
+
if(!App\Modules\Acl\Acl::hasAnyFormPermission()) {
|
163 |
+
return [];
|
164 |
+
}
|
165 |
$data = array ();
|
166 |
foreach ($this->getPredefinedForms() as $key => $item) {
|
167 |
$jsonArray = json_decode($item['json'], true);
|
169 |
|
170 |
$data[$key] = array (
|
171 |
'brief' => $item['brief'],
|
172 |
+
'is_pro' => ArrayHelper::get($item, 'is_pro'),
|
173 |
'title' => $jsonArray['title'],
|
174 |
'buy_url' => 'https://wpfluentform.com/pro',
|
175 |
'screenshot' => $item['screenshot'],
|
app/Modules/Form/Settings/FormCssJs.php
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace FluentForm\App\Modules\Form\Settings;
|
4 |
+
|
5 |
+
use FluentForm\App\Modules\Acl\Acl;
|
6 |
+
|
7 |
+
class FormCssJs
|
8 |
+
{
|
9 |
+
|
10 |
+
public function addCss($form)
|
11 |
+
{
|
12 |
+
$css = $this->getData($form->id, '_custom_form_css');
|
13 |
+
if($css) {
|
14 |
+
?>
|
15 |
+
<style type="text/css">
|
16 |
+
<?php echo $css; ?>
|
17 |
+
</style>
|
18 |
+
<?php
|
19 |
+
}
|
20 |
+
}
|
21 |
+
|
22 |
+
public function addJs($form)
|
23 |
+
{
|
24 |
+
$customJS = $this->getData($form->id, '_custom_form_js');
|
25 |
+
if($customJS) {
|
26 |
+
add_action('wp_footer', function () use ($form, $customJS) {
|
27 |
+
?>
|
28 |
+
<script type="text/javascript">
|
29 |
+
jQuery(document.body).on('fluentform_init', function (event, data) {
|
30 |
+
var $form = jQuery(data[0]);
|
31 |
+
var $ = jQuery;
|
32 |
+
try {
|
33 |
+
<?php echo $customJS; ?>
|
34 |
+
} catch (e) {
|
35 |
+
console.warn('Error in custom JS of Fluentform ID: '+$form.data('form_id'));
|
36 |
+
console.error(e);
|
37 |
+
}
|
38 |
+
});
|
39 |
+
</script>
|
40 |
+
<?php
|
41 |
+
}, 100);
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Get settings for a particular form by id
|
47 |
+
* @return void
|
48 |
+
*/
|
49 |
+
public function getSettingsAjax()
|
50 |
+
{
|
51 |
+
Acl::verify('fluentform_settings_manager');
|
52 |
+
|
53 |
+
$formId = absint($_REQUEST['form_id']);
|
54 |
+
wp_send_json_success(array(
|
55 |
+
'custom_css' => $this->getData($formId, '_custom_form_css'),
|
56 |
+
'custom_js' => $this->getData($formId, '_custom_form_js'),
|
57 |
+
), 200);
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Save settings for a particular form by id
|
62 |
+
* @return void
|
63 |
+
*/
|
64 |
+
public function saveSettingsAjax()
|
65 |
+
{
|
66 |
+
Acl::verify('fluentform_settings_manager');
|
67 |
+
|
68 |
+
$formId = absint($_REQUEST['form_id']);
|
69 |
+
|
70 |
+
$css = $_REQUEST['custom_css'];
|
71 |
+
$js = $_REQUEST['custom_js'];
|
72 |
+
$css = wp_strip_all_tags($css);
|
73 |
+
$js = wp_unslash($js);
|
74 |
+
|
75 |
+
$this->store($formId, '_custom_form_css', $css);
|
76 |
+
$this->store($formId, '_custom_form_js', $js);
|
77 |
+
|
78 |
+
wp_send_json_success([
|
79 |
+
'message' => __('Custom CSS and JS successfully updated', 'fluentform')
|
80 |
+
], 200);
|
81 |
+
}
|
82 |
+
|
83 |
+
|
84 |
+
protected function getData($formId, $metaKey)
|
85 |
+
{
|
86 |
+
$row = wpFluent()->table('fluentform_form_meta')
|
87 |
+
->where('form_id', $formId)
|
88 |
+
->where('meta_key', $metaKey)
|
89 |
+
->first();
|
90 |
+
if ($row) {
|
91 |
+
return $row->value;
|
92 |
+
}
|
93 |
+
return '';
|
94 |
+
}
|
95 |
+
|
96 |
+
|
97 |
+
protected function store($formId, $metaKey, $metaValue)
|
98 |
+
{
|
99 |
+
$row = wpFluent()->table('fluentform_form_meta')
|
100 |
+
->where('form_id', $formId)
|
101 |
+
->where('meta_key', $metaKey)
|
102 |
+
->first();
|
103 |
+
|
104 |
+
if (!$row) {
|
105 |
+
return wpFluent()->table('fluentform_form_meta')
|
106 |
+
->insert([
|
107 |
+
'form_id' => $formId,
|
108 |
+
'meta_key' => $metaKey,
|
109 |
+
'value' => $metaValue
|
110 |
+
]);
|
111 |
+
}
|
112 |
+
|
113 |
+
return wpFluent()->table('fluentform_form_meta')
|
114 |
+
->where('id', $row->id)
|
115 |
+
->update([
|
116 |
+
'value' => $metaValue
|
117 |
+
]);
|
118 |
+
}
|
119 |
+
}
|
app/Modules/Form/Settings/Validator/Confirmations.php
CHANGED
@@ -48,10 +48,10 @@ class Confirmations
|
|
48 |
'customUrl' => 'required_if:redirectTo,customUrl',
|
49 |
],
|
50 |
[
|
51 |
-
'redirectTo.required' => 'The Confirmation Type field is required.',
|
52 |
-
'customPage.required_if' => 'The Page field is required when Confirmation Type is Page.',
|
53 |
-
'customUrl.required_if' => 'The Redirect URL field is required when Confirmation Type is Redirect.',
|
54 |
-
'customUrl.url' => 'The Redirect URL format is invalid.',
|
55 |
]
|
56 |
];
|
57 |
}
|
48 |
'customUrl' => 'required_if:redirectTo,customUrl',
|
49 |
],
|
50 |
[
|
51 |
+
'redirectTo.required' => __('The Confirmation Type field is required.', 'fluentform'),
|
52 |
+
'customPage.required_if' => __('The Page field is required when Confirmation Type is Page.', 'fluentform'),
|
53 |
+
'customUrl.required_if' => __('The Redirect URL field is required when Confirmation Type is Redirect.', 'fluentform'),
|
54 |
+
'customUrl.url' => __('The Redirect URL format is invalid.', 'fluentform'),
|
55 |
]
|
56 |
];
|
57 |
}
|
app/Modules/Integration/ActiveCampaign/Integrator.php
DELETED
@@ -1,129 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Modules\Integration\ActiveCampaign;
|
4 |
-
|
5 |
-
use FluentForm\Framework\Foundation\Application;
|
6 |
-
use FluentForm\App\Modules\Integration\BaseIntegration;
|
7 |
-
|
8 |
-
class Integrator extends BaseIntegration
|
9 |
-
{
|
10 |
-
/**
|
11 |
-
* Option `key` for Active Campaign List settings.
|
12 |
-
*
|
13 |
-
* @var string $key
|
14 |
-
*/
|
15 |
-
private $key = 'activecampaign_details';
|
16 |
-
|
17 |
-
/**
|
18 |
-
* Application instance
|
19 |
-
*
|
20 |
-
* @var \FluentForm\Framework\Foundation\Application $app
|
21 |
-
*/
|
22 |
-
private $app;
|
23 |
-
|
24 |
-
/**
|
25 |
-
* Global settings for the integration.
|
26 |
-
*
|
27 |
-
* @var array $settings
|
28 |
-
*/
|
29 |
-
protected $settings;
|
30 |
-
|
31 |
-
/**
|
32 |
-
* Active Campaign integration constructor.
|
33 |
-
*
|
34 |
-
* @param \FluentForm\Framework\Foundation\Application $app
|
35 |
-
*/
|
36 |
-
public function __construct(Application $app)
|
37 |
-
{
|
38 |
-
parent::__construct(
|
39 |
-
$this->key,
|
40 |
-
$app->request->get('form_id', false),
|
41 |
-
true
|
42 |
-
);
|
43 |
-
|
44 |
-
$this->app = $app;
|
45 |
-
}
|
46 |
-
|
47 |
-
/**
|
48 |
-
* Get all of the integrations.
|
49 |
-
*
|
50 |
-
* @return void
|
51 |
-
*/
|
52 |
-
public function index()
|
53 |
-
{
|
54 |
-
$globalStatus = $this->isActive();
|
55 |
-
|
56 |
-
$integrations = $this->getAll();
|
57 |
-
|
58 |
-
wp_send_json_success([
|
59 |
-
'global_status' => $globalStatus,
|
60 |
-
'integrations' => $integrations,
|
61 |
-
'configure_url' => admin_url('admin.php?page=fluent_forms_settings#activeCampaign')
|
62 |
-
], 200);
|
63 |
-
}
|
64 |
-
|
65 |
-
/**
|
66 |
-
* Determine if the integration is active.
|
67 |
-
*
|
68 |
-
* @return boolean
|
69 |
-
*/
|
70 |
-
private function isActive()
|
71 |
-
{
|
72 |
-
$this->settings = get_option('_fluentform_' . $this->key);
|
73 |
-
|
74 |
-
return $this->settings && $this->settings['status'];
|
75 |
-
}
|
76 |
-
|
77 |
-
/**
|
78 |
-
* Get all available lists.
|
79 |
-
*
|
80 |
-
* @return void
|
81 |
-
*/
|
82 |
-
public function getLists()
|
83 |
-
{
|
84 |
-
if (!$this->isActive()) {
|
85 |
-
wp_send_json_error([
|
86 |
-
'error' => __('ActiveCampaign is not configured yet', 'fluentform')
|
87 |
-
], 400);
|
88 |
-
}
|
89 |
-
|
90 |
-
try {
|
91 |
-
$activeCampaignApi = new \ActiveCampaign($this->settings['apiUrl'], $this->settings['apiKey']);
|
92 |
-
|
93 |
-
$lists = $activeCampaignApi->api('list/list', ['ids' => 'all']);
|
94 |
-
|
95 |
-
if (!$lists->success) {
|
96 |
-
throw new \Exception(__('No lists found.', 'fluentform'));
|
97 |
-
}
|
98 |
-
} catch (\Exception $exception) {
|
99 |
-
wp_send_json_error([
|
100 |
-
'message' => $exception->getMessage()
|
101 |
-
], 400);
|
102 |
-
}
|
103 |
-
|
104 |
-
wp_send_json_success([
|
105 |
-
'lists' => $this->formatLists($lists)
|
106 |
-
], 200);
|
107 |
-
}
|
108 |
-
|
109 |
-
/**
|
110 |
-
* Format the lists excluding some keys so that we only have
|
111 |
-
* the plain array of the lists containing each list info.
|
112 |
-
*
|
113 |
-
* @param array $lists
|
114 |
-
* @return array
|
115 |
-
*/
|
116 |
-
private function formatLists($lists)
|
117 |
-
{
|
118 |
-
$excludeKeys = [
|
119 |
-
'result_code',
|
120 |
-
'result_message',
|
121 |
-
'result_output',
|
122 |
-
'http_code',
|
123 |
-
'success',
|
124 |
-
'error',
|
125 |
-
];
|
126 |
-
|
127 |
-
return array_values(array_diff_key((array) $lists, array_flip($excludeKeys)));
|
128 |
-
}
|
129 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Modules/Integration/BaseIntegration.php
DELETED
@@ -1,103 +0,0 @@
|
|
1 |
-
<?php namespace FluentForm\App\Modules\Integration;
|
2 |
-
|
3 |
-
class BaseIntegration
|
4 |
-
{
|
5 |
-
private $setting_key = '';
|
6 |
-
private $isMultiple = false;
|
7 |
-
private $formId = false;
|
8 |
-
private $isJsonValue = true;
|
9 |
-
|
10 |
-
public function __construct($settings_key = '', $form_id = false, $isMultiple = false)
|
11 |
-
{
|
12 |
-
$this->setting_key = $settings_key;
|
13 |
-
$this->isMultiple = $isMultiple;
|
14 |
-
$this->formId = $form_id;
|
15 |
-
}
|
16 |
-
|
17 |
-
public function setSettingsKey($key)
|
18 |
-
{
|
19 |
-
$this->setting_key = $key;
|
20 |
-
}
|
21 |
-
|
22 |
-
public function setIsMultiple($isMultiple)
|
23 |
-
{
|
24 |
-
$this->isMultiple = $isMultiple;
|
25 |
-
}
|
26 |
-
|
27 |
-
public function setFormId($formId)
|
28 |
-
{
|
29 |
-
$this->formId = $formId;
|
30 |
-
}
|
31 |
-
|
32 |
-
public function setJasonType($type)
|
33 |
-
{
|
34 |
-
$this->isJsonValue = $type;
|
35 |
-
}
|
36 |
-
|
37 |
-
public function save($settings)
|
38 |
-
{
|
39 |
-
return wpFluent()->table('fluentform_form_meta')
|
40 |
-
->insert(array(
|
41 |
-
'meta_key' => $this->setting_key,
|
42 |
-
'form_id' => $this->formId,
|
43 |
-
'value' => json_encode($settings)
|
44 |
-
));
|
45 |
-
}
|
46 |
-
|
47 |
-
public function update($settingsId, $settings)
|
48 |
-
{
|
49 |
-
return wpFluent()->table('fluentform_form_meta')
|
50 |
-
->where('id', $settingsId)
|
51 |
-
->update(array(
|
52 |
-
'value' => json_encode($settings)
|
53 |
-
));
|
54 |
-
}
|
55 |
-
|
56 |
-
public function get($settingsId)
|
57 |
-
{
|
58 |
-
$settings = wpFluent()->table('fluentform_form_meta')
|
59 |
-
->where('form_id', $this->formId)
|
60 |
-
->where('meta_key', $this->setting_key)
|
61 |
-
->find($settingsId);
|
62 |
-
$settings->formattedValue = $this->getFormattedValue($settings);
|
63 |
-
return $settings;
|
64 |
-
}
|
65 |
-
|
66 |
-
public function getAll()
|
67 |
-
{
|
68 |
-
$settingsQuery = wpFluent()->table('fluentform_form_meta')
|
69 |
-
->where('form_id', $this->formId)
|
70 |
-
->where('meta_key', $this->setting_key);
|
71 |
-
if($this->isMultiple) {
|
72 |
-
$settings = $settingsQuery->get();
|
73 |
-
foreach ($settings as $setting) {
|
74 |
-
$setting->formattedValue = $this->getFormattedValue($setting);
|
75 |
-
}
|
76 |
-
} else {
|
77 |
-
$settings = $settingsQuery->first();
|
78 |
-
$settings->formattedValue = $this->getFormattedValue($settings);
|
79 |
-
}
|
80 |
-
return $settings;
|
81 |
-
}
|
82 |
-
|
83 |
-
public function delete($settingsId)
|
84 |
-
{
|
85 |
-
return wpFluent()->table('fluentform_form_meta')
|
86 |
-
->where('meta_key', $this->setting_key)
|
87 |
-
->where('form_id', $this->formId)
|
88 |
-
->where('id', $settingsId)
|
89 |
-
->delete();
|
90 |
-
}
|
91 |
-
|
92 |
-
public function deleteAll()
|
93 |
-
{
|
94 |
-
|
95 |
-
}
|
96 |
-
|
97 |
-
private function getFormattedValue($setting)
|
98 |
-
{
|
99 |
-
if($this->isJsonValue)
|
100 |
-
return json_decode($setting->value, true);
|
101 |
-
return $setting->value;
|
102 |
-
}
|
103 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Modules/Integration/MailChimpIntegration.php
DELETED
@@ -1,172 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Modules\Integration;
|
4 |
-
|
5 |
-
use FluentForm\Framework\Foundation\Application;
|
6 |
-
use FluentForm\App\Services\Integrations\MailChimp;
|
7 |
-
use FluentForm\App\Modules\Integration\MailChimpSubscriber as Subscriber;
|
8 |
-
|
9 |
-
class MailChimpIntegration extends BaseIntegration
|
10 |
-
{
|
11 |
-
/**
|
12 |
-
* MailChimp Subscriber that handles & process all the subscribing logics.
|
13 |
-
*/
|
14 |
-
use Subscriber;
|
15 |
-
|
16 |
-
private $key = 'mailchimp_feeds';
|
17 |
-
|
18 |
-
private $app;
|
19 |
-
|
20 |
-
public function __construct(Application $application)
|
21 |
-
{
|
22 |
-
parent::__construct($this->key, $application->request->get('form_id', false), true);
|
23 |
-
$this->app = $application;
|
24 |
-
}
|
25 |
-
|
26 |
-
public function getMailChimpSettings()
|
27 |
-
{
|
28 |
-
$globalStatus = $this->isConfigured();
|
29 |
-
$integrations = $this->getAll();
|
30 |
-
|
31 |
-
wp_send_json_success(array(
|
32 |
-
'global_status' => $globalStatus,
|
33 |
-
'integrations' => $integrations,
|
34 |
-
'configure_url' => admin_url('admin.php?page=fluent_forms_settings#mailchimp')
|
35 |
-
), 200);
|
36 |
-
|
37 |
-
}
|
38 |
-
|
39 |
-
public function getMailChimpLists()
|
40 |
-
{
|
41 |
-
if (! $this->isConfigured()) {
|
42 |
-
wp_send_json_error(array(
|
43 |
-
'error' => __('MailChimp is not configured yet', 'fluentform')
|
44 |
-
), 400);
|
45 |
-
}
|
46 |
-
$settings = get_option('_fluentform_mailchimp_details');
|
47 |
-
|
48 |
-
try {
|
49 |
-
$MailChimp = new MailChimp($settings['apiKey']);
|
50 |
-
$lists = $MailChimp->get('lists', array('count' => 9999));
|
51 |
-
if (! $MailChimp->success()) {
|
52 |
-
throw new \Exception($MailChimp->getLastError());
|
53 |
-
}
|
54 |
-
} catch (\Exception $exception) {
|
55 |
-
wp_send_json_error(array(
|
56 |
-
'message' => $exception->getMessage()
|
57 |
-
), 400);
|
58 |
-
}
|
59 |
-
|
60 |
-
$formattedLists = array();
|
61 |
-
|
62 |
-
foreach ($lists['lists'] as $list) {
|
63 |
-
$formattedLists[$list['id']] = $list;
|
64 |
-
}
|
65 |
-
|
66 |
-
wp_send_json_success(array(
|
67 |
-
'lists' => $formattedLists
|
68 |
-
), 200);
|
69 |
-
|
70 |
-
}
|
71 |
-
|
72 |
-
public function getMailChimpList()
|
73 |
-
{
|
74 |
-
if (! $this->isConfigured()) {
|
75 |
-
wp_send_json_error(array(
|
76 |
-
'error' => __('MailChimp is not configured yet', 'fluentform')
|
77 |
-
), 400);
|
78 |
-
}
|
79 |
-
$settings = get_option('_fluentform_mailchimp_details');
|
80 |
-
$list_id = $this->app->request->get('listId');
|
81 |
-
|
82 |
-
try {
|
83 |
-
$MailChimp = new MailChimp($settings['apiKey']);
|
84 |
-
$list = $MailChimp->get('lists/'.$list_id.'/merge-fields', array('count' => 9999));
|
85 |
-
if (! $MailChimp->success()) {
|
86 |
-
throw new \Exception($MailChimp->getLastError());
|
87 |
-
}
|
88 |
-
} catch (\Exception $exception) {
|
89 |
-
wp_send_json_error(array(
|
90 |
-
'message' => $exception->getMessage()
|
91 |
-
), 400);
|
92 |
-
}
|
93 |
-
|
94 |
-
$mergedFields = $list['merge_fields'];
|
95 |
-
$fields = array();
|
96 |
-
|
97 |
-
foreach ($mergedFields as $merged_field) {
|
98 |
-
$fields[$merged_field['tag']] = $merged_field['name'];
|
99 |
-
}
|
100 |
-
|
101 |
-
wp_send_json_success(array(
|
102 |
-
'merge_fields' => $fields
|
103 |
-
), 200);
|
104 |
-
}
|
105 |
-
|
106 |
-
public function saveNotification()
|
107 |
-
{
|
108 |
-
if (! $this->isConfigured()) {
|
109 |
-
wp_send_json_error(array(
|
110 |
-
'error' => __('MailChimp is not configured yet', 'fluentform')
|
111 |
-
), 400);
|
112 |
-
}
|
113 |
-
$notification = $this->app->request->get('notification');
|
114 |
-
$notification_id = $this->app->request->get('notification_id');
|
115 |
-
$notification = json_decode($notification, true);
|
116 |
-
|
117 |
-
// validate notification now
|
118 |
-
$this->validate($notification);
|
119 |
-
$notification = fluentFormSanitizer($notification);
|
120 |
-
|
121 |
-
if ($notification_id) {
|
122 |
-
$this->update($notification_id, $notification);
|
123 |
-
$message = __('MailChimp Field successfully updated', 'fluentform');
|
124 |
-
} else {
|
125 |
-
$notification_id = $this->save($notification);
|
126 |
-
$message = __('MailChimp Field successfully created', 'fluentform');
|
127 |
-
}
|
128 |
-
|
129 |
-
wp_send_json_success(array(
|
130 |
-
'message' => $message,
|
131 |
-
'notification_id' => $notification_id
|
132 |
-
), 200);
|
133 |
-
|
134 |
-
}
|
135 |
-
|
136 |
-
public function deleteNotification()
|
137 |
-
{
|
138 |
-
$settingsId = $this->app->request->get('id');
|
139 |
-
$this->delete($settingsId);
|
140 |
-
wp_send_json_success(array(
|
141 |
-
'message' => __('Selected MailChimp Feed is deleted', 'fluentform'),
|
142 |
-
'integrations' => $this->getAll()
|
143 |
-
));
|
144 |
-
}
|
145 |
-
|
146 |
-
private function isConfigured()
|
147 |
-
{
|
148 |
-
$globalStatus = get_option('_fluentform_mailchimp_details');
|
149 |
-
return $globalStatus && $globalStatus['status'];
|
150 |
-
}
|
151 |
-
|
152 |
-
private function validate($notification)
|
153 |
-
{
|
154 |
-
$validate = fluentValidator($notification, array(
|
155 |
-
'name' => 'required',
|
156 |
-
'list_id' => 'required',
|
157 |
-
'fieldEmailAddress' => 'required'
|
158 |
-
), array(
|
159 |
-
'name.required' => __('MailChimp Feed Name is required', 'fluentform'),
|
160 |
-
'list.required' => __(' MailChimp List is required', 'fluentform'),
|
161 |
-
'fieldEmailAddress.required' => __('Email Address is required')
|
162 |
-
))->validate();
|
163 |
-
|
164 |
-
if ($validate->fails()) {
|
165 |
-
wp_send_json_error(array(
|
166 |
-
'errors' => $validate->errors(),
|
167 |
-
'message' => __('Please fix the errors', 'fluentform')
|
168 |
-
), 400);
|
169 |
-
}
|
170 |
-
return true;
|
171 |
-
}
|
172 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Modules/Integration/MailChimpSubscriber.php
DELETED
@@ -1,97 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Modules\Integration;
|
4 |
-
|
5 |
-
use FluentForm\App\Services\ConditionAssesor;
|
6 |
-
use FluentForm\Framework\Helpers\ArrayHelper;
|
7 |
-
use FluentForm\App\Services\Integrations\MailChimp;
|
8 |
-
|
9 |
-
trait MailChimpSubscriber
|
10 |
-
{
|
11 |
-
/**
|
12 |
-
* Enabled MailChimp feed settings.
|
13 |
-
*
|
14 |
-
* @var array $feeds
|
15 |
-
*/
|
16 |
-
protected $feeds = [];
|
17 |
-
|
18 |
-
/**
|
19 |
-
* Form input data.
|
20 |
-
*
|
21 |
-
* @param array $formData
|
22 |
-
*/
|
23 |
-
public function setApplicableFeeds($formData)
|
24 |
-
{
|
25 |
-
$feeds = $this->getAll();
|
26 |
-
|
27 |
-
foreach ($feeds as $feed) {
|
28 |
-
if ($this->isApplicable($feed, $formData)) {
|
29 |
-
$email = ArrayHelper::get($formData, ArrayHelper::get($feed->formattedValue, 'fieldEmailAddress'));
|
30 |
-
|
31 |
-
if (is_string($email) && is_email($email)) {
|
32 |
-
$feed->formattedValue['fieldEmailAddress'] = $email;
|
33 |
-
|
34 |
-
$this->feeds[] = $feed;
|
35 |
-
}
|
36 |
-
}
|
37 |
-
}
|
38 |
-
}
|
39 |
-
|
40 |
-
/**
|
41 |
-
* Determine if the feed is eligible to be applied.
|
42 |
-
*
|
43 |
-
* @param $feed
|
44 |
-
* @param $formData
|
45 |
-
*
|
46 |
-
* @return bool
|
47 |
-
*/
|
48 |
-
public function isApplicable(&$feed, &$formData)
|
49 |
-
{
|
50 |
-
return ArrayHelper::get($feed->formattedValue, 'enabled') &&
|
51 |
-
ArrayHelper::get($feed->formattedValue, 'list_id') &&
|
52 |
-
ConditionAssesor::evaluate($feed->formattedValue, $formData);
|
53 |
-
}
|
54 |
-
|
55 |
-
/**
|
56 |
-
* Subscribe a user to the list on form submission.
|
57 |
-
*
|
58 |
-
* @param $formData
|
59 |
-
*/
|
60 |
-
public function subscribe($formData)
|
61 |
-
{
|
62 |
-
if ($this->isConfigured()) {
|
63 |
-
|
64 |
-
// Prepare applicable feeds.
|
65 |
-
$this->setApplicableFeeds($formData);
|
66 |
-
|
67 |
-
foreach ($this->feeds as $feed) {
|
68 |
-
$mergeFields = [];
|
69 |
-
|
70 |
-
foreach (ArrayHelper::get($feed->formattedValue, 'merge_fields', []) as $field => $getter) {
|
71 |
-
$value = ArrayHelper::get($formData, $getter, '');
|
72 |
-
|
73 |
-
$mergeFields[$field] = is_array($value) ? implode(' ', $value) : $value;
|
74 |
-
}
|
75 |
-
|
76 |
-
$status = $feed->formattedValue['doubleOptIn'] ? 'pending' : 'subscribed';
|
77 |
-
|
78 |
-
$arguments = [
|
79 |
-
'email_address' => $feed->formattedValue['fieldEmailAddress'],
|
80 |
-
'status' => $status,
|
81 |
-
'merge_fields' => (object) $mergeFields,
|
82 |
-
'double_optin' => $feed->formattedValue['doubleOptIn'],
|
83 |
-
'vip' => $feed->formattedValue['markAsVIP'],
|
84 |
-
];
|
85 |
-
|
86 |
-
$settings = get_option('_fluentform_mailchimp_details');
|
87 |
-
|
88 |
-
$MailChimp = new MailChimp($settings['apiKey']);
|
89 |
-
|
90 |
-
$endPoint = 'lists/'.$feed->formattedValue['list_id'].'/members/'
|
91 |
-
.md5(strtolower($feed->formattedValue['fieldEmailAddress']));
|
92 |
-
|
93 |
-
$MailChimp->put($endPoint, $arguments);
|
94 |
-
}
|
95 |
-
}
|
96 |
-
}
|
97 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Modules/Registerer/Menu.php
CHANGED
@@ -36,14 +36,14 @@ class Menu
|
|
36 |
$dashBoardCapability = apply_filters('fluentform_dashboard_capability', 'fluentform_settings_manager');
|
37 |
$settingsCapability = apply_filters('fluentform_settings_capability', 'fluentform_settings_manager');
|
38 |
|
39 |
-
if(!current_user_can($dashBoardCapability) && !current_user_can($settingsCapability)) {
|
40 |
return;
|
41 |
}
|
42 |
|
43 |
if (defined('FLUENTFORMPRO')) {
|
44 |
-
$title =
|
45 |
} else {
|
46 |
-
$title =
|
47 |
}
|
48 |
|
49 |
|
@@ -169,7 +169,7 @@ class Menu
|
|
169 |
'settings' => array(
|
170 |
'slug' => 'settings',
|
171 |
'hash' => 'basic_settings',
|
172 |
-
'title' => __('Settings', 'fluentform'),
|
173 |
'sub_route' => 'form_settings'
|
174 |
),
|
175 |
'entries' => array(
|
@@ -239,6 +239,9 @@ class Menu
|
|
239 |
);
|
240 |
}
|
241 |
|
|
|
|
|
|
|
242 |
$settingsMenus = apply_filters('fluentform_form_settings_menu', $settingsMenus, $form_id);
|
243 |
|
244 |
$externalMenuItems = [];
|
@@ -249,8 +252,15 @@ class Menu
|
|
249 |
}
|
250 |
}
|
251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
$settingsMenus = array_filter(array_merge($settingsMenus, $externalMenuItems));
|
253 |
|
|
|
254 |
$currentRoute = sanitize_text_field($this->app->request->get('sub_route', ''));
|
255 |
|
256 |
View::render('admin.form.settings_wrapper', array(
|
@@ -291,8 +301,9 @@ class Menu
|
|
291 |
wp_localize_script('fluentform_form_settings', 'FluentFormApp', array(
|
292 |
'form_id' => $form_id,
|
293 |
'plugin' => $this->app->getSlug(),
|
294 |
-
'hasPro' =>
|
295 |
-
'hasPDF' => class_exists('FluentFormPdf\FluentFormPdf')
|
|
|
296 |
));
|
297 |
|
298 |
View::render('admin.form.settings', array(
|
@@ -315,6 +326,7 @@ class Menu
|
|
315 |
wp_localize_script('fluent_all_forms', 'FluentFormApp', array(
|
316 |
'plugin' => $this->app->getSlug(),
|
317 |
'formsCount' => $formsCount,
|
|
|
318 |
'adminUrl' => admin_url('admin.php?page=fluent_forms'),
|
319 |
'isDisableAnalytics' => apply_filters('fluentform-disabled_analytics', false)
|
320 |
));
|
@@ -439,9 +451,14 @@ class Menu
|
|
439 |
['jquery'], FLUENTFORM_VERSION, false
|
440 |
);
|
441 |
|
|
|
|
|
|
|
|
|
|
|
442 |
wp_localize_script('fluentform-transfer-js', 'FluentFormApp', [
|
443 |
'plugin' => $this->app->getSlug(),
|
444 |
-
'forms' =>
|
445 |
]);
|
446 |
|
447 |
View::render('admin.transfer.index');
|
@@ -489,4 +506,9 @@ class Menu
|
|
489 |
});
|
490 |
}
|
491 |
}
|
|
|
|
|
|
|
|
|
|
|
492 |
}
|
36 |
$dashBoardCapability = apply_filters('fluentform_dashboard_capability', 'fluentform_settings_manager');
|
37 |
$settingsCapability = apply_filters('fluentform_settings_capability', 'fluentform_settings_manager');
|
38 |
|
39 |
+
if (!current_user_can($dashBoardCapability) && !current_user_can($settingsCapability)) {
|
40 |
return;
|
41 |
}
|
42 |
|
43 |
if (defined('FLUENTFORMPRO')) {
|
44 |
+
$title = __('Fluent Form Pro', 'fluentform');
|
45 |
} else {
|
46 |
+
$title = __('Fluent Form', 'fluentform');
|
47 |
}
|
48 |
|
49 |
|
169 |
'settings' => array(
|
170 |
'slug' => 'settings',
|
171 |
'hash' => 'basic_settings',
|
172 |
+
'title' => __('Settings & Integrations', 'fluentform'),
|
173 |
'sub_route' => 'form_settings'
|
174 |
),
|
175 |
'entries' => array(
|
239 |
);
|
240 |
}
|
241 |
|
242 |
+
|
243 |
+
|
244 |
+
|
245 |
$settingsMenus = apply_filters('fluentform_form_settings_menu', $settingsMenus, $form_id);
|
246 |
|
247 |
$externalMenuItems = [];
|
252 |
}
|
253 |
}
|
254 |
|
255 |
+
$settingsMenus['custom_css_js'] = array(
|
256 |
+
'title' => __('Custom CSS/JS', 'fluentform'),
|
257 |
+
'slug' => 'form_settings',
|
258 |
+
'hash' => 'custom_css_js'
|
259 |
+
);
|
260 |
+
|
261 |
$settingsMenus = array_filter(array_merge($settingsMenus, $externalMenuItems));
|
262 |
|
263 |
+
|
264 |
$currentRoute = sanitize_text_field($this->app->request->get('sub_route', ''));
|
265 |
|
266 |
View::render('admin.form.settings_wrapper', array(
|
301 |
wp_localize_script('fluentform_form_settings', 'FluentFormApp', array(
|
302 |
'form_id' => $form_id,
|
303 |
'plugin' => $this->app->getSlug(),
|
304 |
+
'hasPro' => defined('FLUENTFORMPRO'),
|
305 |
+
'hasPDF' => class_exists('FluentFormPdf\FluentFormPdf'),
|
306 |
+
'ace_path_url' => $this->app->publicUrl('libs/ace')
|
307 |
));
|
308 |
|
309 |
View::render('admin.form.settings', array(
|
326 |
wp_localize_script('fluent_all_forms', 'FluentFormApp', array(
|
327 |
'plugin' => $this->app->getSlug(),
|
328 |
'formsCount' => $formsCount,
|
329 |
+
'hasPro' => defined('FLUENTFORMPRO'),
|
330 |
'adminUrl' => admin_url('admin.php?page=fluent_forms'),
|
331 |
'isDisableAnalytics' => apply_filters('fluentform-disabled_analytics', false)
|
332 |
));
|
451 |
['jquery'], FLUENTFORM_VERSION, false
|
452 |
);
|
453 |
|
454 |
+
$forms = wpFluent()->table('fluentform_forms')
|
455 |
+
->orderBy('id', 'desc')
|
456 |
+
->select(['id', 'title'])
|
457 |
+
->get();
|
458 |
+
|
459 |
wp_localize_script('fluentform-transfer-js', 'FluentFormApp', [
|
460 |
'plugin' => $this->app->getSlug(),
|
461 |
+
'forms' => $forms
|
462 |
]);
|
463 |
|
464 |
View::render('admin.transfer.index');
|
506 |
});
|
507 |
}
|
508 |
}
|
509 |
+
|
510 |
+
public function renderGlobalMenu()
|
511 |
+
{
|
512 |
+
View::render('admin.global_menu', array());
|
513 |
+
}
|
514 |
}
|
app/Providers/ActiveCampaignApiProvider.php
DELETED
@@ -1,13 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Providers;
|
4 |
-
|
5 |
-
use FluentForm\Framework\Foundation\Provider;
|
6 |
-
|
7 |
-
class ActiveCampaignApiProvider extends Provider
|
8 |
-
{
|
9 |
-
public function booting()
|
10 |
-
{
|
11 |
-
require_once $this->app->appPath() . 'Services/Integrations/activecampaign-api-php/includes/ActiveCampaign.class.php';
|
12 |
-
}
|
13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Providers/BackgroundProcessingProvider.php
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
<?php namespace FluentForm\App\Providers;
|
2 |
-
|
3 |
-
use FluentForm\Framework\Foundation\Provider;
|
4 |
-
|
5 |
-
class BackgroundProcessingProvider extends Provider
|
6 |
-
{
|
7 |
-
public function booting()
|
8 |
-
{
|
9 |
-
require_once $this->app->appPath().'Services/BackgroundProcessing/wp-background-processing.php';
|
10 |
-
}
|
11 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Providers/CsvProvider.php
DELETED
@@ -1,13 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Providers;
|
4 |
-
|
5 |
-
use FluentForm\Framework\Foundation\Provider;
|
6 |
-
|
7 |
-
class CsvProvider extends Provider
|
8 |
-
{
|
9 |
-
public function booting()
|
10 |
-
{
|
11 |
-
require_once $this->app->appPath().'Services/csv/autoload.php';
|
12 |
-
}
|
13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/BackgroundProcessing/classes/wp-async-request.php
DELETED
@@ -1,163 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* WP Async Request
|
4 |
-
*
|
5 |
-
* @package WP-Background-Processing
|
6 |
-
*/
|
7 |
-
|
8 |
-
if ( ! class_exists( 'WP_Async_Request' ) ) {
|
9 |
-
|
10 |
-
/**
|
11 |
-
* Abstract WP_Async_Request class.
|
12 |
-
*
|
13 |
-
* @abstract
|
14 |
-
*/
|
15 |
-
abstract class WP_Async_Request {
|
16 |
-
|
17 |
-
/**
|
18 |
-
* Prefix
|
19 |
-
*
|
20 |
-
* (default value: 'wp')
|
21 |
-
*
|
22 |
-
* @var string
|
23 |
-
* @access protected
|
24 |
-
*/
|
25 |
-
protected $prefix = 'wp';
|
26 |
-
|
27 |
-
/**
|
28 |
-
* Action
|
29 |
-
*
|
30 |
-
* (default value: 'async_request')
|
31 |
-
*
|
32 |
-
* @var string
|
33 |
-
* @access protected
|
34 |
-
*/
|
35 |
-
protected $action = 'async_request';
|
36 |
-
|
37 |
-
/**
|
38 |
-
* Identifier
|
39 |
-
*
|
40 |
-
* @var mixed
|
41 |
-
* @access protected
|
42 |
-
*/
|
43 |
-
protected $identifier;
|
44 |
-
|
45 |
-
/**
|
46 |
-
* Data
|
47 |
-
*
|
48 |
-
* (default value: array())
|
49 |
-
*
|
50 |
-
* @var array
|
51 |
-
* @access protected
|
52 |
-
*/
|
53 |
-
protected $data = array();
|
54 |
-
|
55 |
-
/**
|
56 |
-
* Initiate new async request
|
57 |
-
*/
|
58 |
-
public function __construct() {
|
59 |
-
$this->identifier = $this->prefix . '_' . $this->action;
|
60 |
-
|
61 |
-
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
62 |
-
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
63 |
-
}
|
64 |
-
|
65 |
-
/**
|
66 |
-
* Set data used during the request
|
67 |
-
*
|
68 |
-
* @param array $data Data.
|
69 |
-
*
|
70 |
-
* @return $this
|
71 |
-
*/
|
72 |
-
public function data( $data ) {
|
73 |
-
$this->data = $data;
|
74 |
-
|
75 |
-
return $this;
|
76 |
-
}
|
77 |
-
|
78 |
-
/**
|
79 |
-
* Dispatch the async request
|
80 |
-
*
|
81 |
-
* @return array|WP_Error
|
82 |
-
*/
|
83 |
-
public function dispatch() {
|
84 |
-
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
|
85 |
-
$args = $this->get_post_args();
|
86 |
-
|
87 |
-
return wp_remote_post( esc_url_raw( $url ), $args );
|
88 |
-
}
|
89 |
-
|
90 |
-
/**
|
91 |
-
* Get query args
|
92 |
-
*
|
93 |
-
* @return array
|
94 |
-
*/
|
95 |
-
protected function get_query_args() {
|
96 |
-
if ( property_exists( $this, 'query_args' ) ) {
|
97 |
-
return $this->query_args;
|
98 |
-
}
|
99 |
-
|
100 |
-
return array(
|
101 |
-
'action' => $this->identifier,
|
102 |
-
'nonce' => wp_create_nonce( $this->identifier ),
|
103 |
-
);
|
104 |
-
}
|
105 |
-
|
106 |
-
/**
|
107 |
-
* Get query URL
|
108 |
-
*
|
109 |
-
* @return string
|
110 |
-
*/
|
111 |
-
protected function get_query_url() {
|
112 |
-
if ( property_exists( $this, 'query_url' ) ) {
|
113 |
-
return $this->query_url;
|
114 |
-
}
|
115 |
-
|
116 |
-
return admin_url( 'admin-ajax.php' );
|
117 |
-
}
|
118 |
-
|
119 |
-
/**
|
120 |
-
* Get post args
|
121 |
-
*
|
122 |
-
* @return array
|
123 |
-
*/
|
124 |
-
protected function get_post_args() {
|
125 |
-
if ( property_exists( $this, 'post_args' ) ) {
|
126 |
-
return $this->post_args;
|
127 |
-
}
|
128 |
-
|
129 |
-
return array(
|
130 |
-
'timeout' => 0.01,
|
131 |
-
'blocking' => false,
|
132 |
-
'body' => $this->data,
|
133 |
-
'cookies' => $_COOKIE,
|
134 |
-
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
|
135 |
-
);
|
136 |
-
}
|
137 |
-
|
138 |
-
/**
|
139 |
-
* Maybe handle
|
140 |
-
*
|
141 |
-
* Check for correct nonce and pass to handler.
|
142 |
-
*/
|
143 |
-
public function maybe_handle() {
|
144 |
-
// Don't lock up other requests while processing
|
145 |
-
session_write_close();
|
146 |
-
|
147 |
-
check_ajax_referer( $this->identifier, 'nonce' );
|
148 |
-
|
149 |
-
$this->handle();
|
150 |
-
|
151 |
-
wp_die();
|
152 |
-
}
|
153 |
-
|
154 |
-
/**
|
155 |
-
* Handle
|
156 |
-
*
|
157 |
-
* Override this method to perform any actions required
|
158 |
-
* during the async request.
|
159 |
-
*/
|
160 |
-
abstract protected function handle();
|
161 |
-
|
162 |
-
}
|
163 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/BackgroundProcessing/classes/wp-background-process.php
DELETED
@@ -1,506 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* WP Background Process
|
4 |
-
*
|
5 |
-
* @package WP-Background-Processing
|
6 |
-
*/
|
7 |
-
|
8 |
-
if ( ! class_exists( 'WP_Background_Process' ) ) {
|
9 |
-
|
10 |
-
/**
|
11 |
-
* Abstract WP_Background_Process class.
|
12 |
-
*
|
13 |
-
* @abstract
|
14 |
-
* @extends WP_Async_Request
|
15 |
-
*/
|
16 |
-
abstract class WP_Background_Process extends WP_Async_Request {
|
17 |
-
|
18 |
-
/**
|
19 |
-
* Action
|
20 |
-
*
|
21 |
-
* (default value: 'background_process')
|
22 |
-
*
|
23 |
-
* @var string
|
24 |
-
* @access protected
|
25 |
-
*/
|
26 |
-
protected $action = 'background_process';
|
27 |
-
|
28 |
-
/**
|
29 |
-
* Start time of current process.
|
30 |
-
*
|
31 |
-
* (default value: 0)
|
32 |
-
*
|
33 |
-
* @var int
|
34 |
-
* @access protected
|
35 |
-
*/
|
36 |
-
protected $start_time = 0;
|
37 |
-
|
38 |
-
/**
|
39 |
-
* Cron_hook_identifier
|
40 |
-
*
|
41 |
-
* @var mixed
|
42 |
-
* @access protected
|
43 |
-
*/
|
44 |
-
protected $cron_hook_identifier;
|
45 |
-
|
46 |
-
/**
|
47 |
-
* Cron_interval_identifier
|
48 |
-
*
|
49 |
-
* @var mixed
|
50 |
-
* @access protected
|
51 |
-
*/
|
52 |
-
protected $cron_interval_identifier;
|
53 |
-
|
54 |
-
/**
|
55 |
-
* Initiate new background process
|
56 |
-
*/
|
57 |
-
public function __construct() {
|
58 |
-
parent::__construct();
|
59 |
-
|
60 |
-
$this->cron_hook_identifier = $this->identifier . '_cron';
|
61 |
-
$this->cron_interval_identifier = $this->identifier . '_cron_interval';
|
62 |
-
|
63 |
-
add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
|
64 |
-
add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
|
65 |
-
}
|
66 |
-
|
67 |
-
/**
|
68 |
-
* Dispatch
|
69 |
-
*
|
70 |
-
* @access public
|
71 |
-
* @return void
|
72 |
-
*/
|
73 |
-
public function dispatch() {
|
74 |
-
// Schedule the cron healthcheck.
|
75 |
-
$this->schedule_event();
|
76 |
-
|
77 |
-
// Perform remote post.
|
78 |
-
return parent::dispatch();
|
79 |
-
}
|
80 |
-
|
81 |
-
/**
|
82 |
-
* Push to queue
|
83 |
-
*
|
84 |
-
* @param mixed $data Data.
|
85 |
-
*
|
86 |
-
* @return $this
|
87 |
-
*/
|
88 |
-
public function push_to_queue( $data ) {
|
89 |
-
$this->data[] = $data;
|
90 |
-
|
91 |
-
return $this;
|
92 |
-
}
|
93 |
-
|
94 |
-
/**
|
95 |
-
* Save queue
|
96 |
-
*
|
97 |
-
* @return $this
|
98 |
-
*/
|
99 |
-
public function save() {
|
100 |
-
$key = $this->generate_key();
|
101 |
-
|
102 |
-
if ( ! empty( $this->data ) ) {
|
103 |
-
update_site_option( $key, $this->data );
|
104 |
-
}
|
105 |
-
|
106 |
-
return $this;
|
107 |
-
}
|
108 |
-
|
109 |
-
/**
|
110 |
-
* Update queue
|
111 |
-
*
|
112 |
-
* @param string $key Key.
|
113 |
-
* @param array $data Data.
|
114 |
-
*
|
115 |
-
* @return $this
|
116 |
-
*/
|
117 |
-
public function update( $key, $data ) {
|
118 |
-
if ( ! empty( $data ) ) {
|
119 |
-
update_site_option( $key, $data );
|
120 |
-
}
|
121 |
-
|
122 |
-
return $this;
|
123 |
-
}
|
124 |
-
|
125 |
-
/**
|
126 |
-
* Delete queue
|
127 |
-
*
|
128 |
-
* @param string $key Key.
|
129 |
-
*
|
130 |
-
* @return $this
|
131 |
-
*/
|
132 |
-
public function delete( $key ) {
|
133 |
-
delete_site_option( $key );
|
134 |
-
|
135 |
-
return $this;
|
136 |
-
}
|
137 |
-
|
138 |
-
/**
|
139 |
-
* Generate key
|
140 |
-
*
|
141 |
-
* Generates a unique key based on microtime. Queue items are
|
142 |
-
* given a unique key so that they can be merged upon save.
|
143 |
-
*
|
144 |
-
* @param int $length Length.
|
145 |
-
*
|
146 |
-
* @return string
|
147 |
-
*/
|
148 |
-
protected function generate_key( $length = 64 ) {
|
149 |
-
$unique = md5( microtime() . rand() );
|
150 |
-
$prepend = $this->identifier . '_batch_';
|
151 |
-
|
152 |
-
return substr( $prepend . $unique, 0, $length );
|
153 |
-
}
|
154 |
-
|
155 |
-
/**
|
156 |
-
* Maybe process queue
|
157 |
-
*
|
158 |
-
* Checks whether data exists within the queue and that
|
159 |
-
* the process is not already running.
|
160 |
-
*/
|
161 |
-
public function maybe_handle() {
|
162 |
-
// Don't lock up other requests while processing
|
163 |
-
session_write_close();
|
164 |
-
|
165 |
-
if ( $this->is_process_running() ) {
|
166 |
-
// Background process already running.
|
167 |
-
wp_die();
|
168 |
-
}
|
169 |
-
|
170 |
-
if ( $this->is_queue_empty() ) {
|
171 |
-
// No data to process.
|
172 |
-
wp_die();
|
173 |
-
}
|
174 |
-
|
175 |
-
check_ajax_referer( $this->identifier, 'nonce' );
|
176 |
-
|
177 |
-
$this->handle();
|
178 |
-
|
179 |
-
wp_die();
|
180 |
-
}
|
181 |
-
|
182 |
-
/**
|
183 |
-
* Is queue empty
|
184 |
-
*
|
185 |
-
* @return bool
|
186 |
-
*/
|
187 |
-
protected function is_queue_empty() {
|
188 |
-
global $wpdb;
|
189 |
-
|
190 |
-
$table = $wpdb->options;
|
191 |
-
$column = 'option_name';
|
192 |
-
|
193 |
-
if ( is_multisite() ) {
|
194 |
-
$table = $wpdb->sitemeta;
|
195 |
-
$column = 'meta_key';
|
196 |
-
}
|
197 |
-
|
198 |
-
$key = $this->identifier . '_batch_%';
|
199 |
-
|
200 |
-
$count = $wpdb->get_var( $wpdb->prepare( "
|
201 |
-
SELECT COUNT(*)
|
202 |
-
FROM {$table}
|
203 |
-
WHERE {$column} LIKE %s
|
204 |
-
", $key ) );
|
205 |
-
|
206 |
-
return ( $count > 0 ) ? false : true;
|
207 |
-
}
|
208 |
-
|
209 |
-
/**
|
210 |
-
* Is process running
|
211 |
-
*
|
212 |
-
* Check whether the current process is already running
|
213 |
-
* in a background process.
|
214 |
-
*/
|
215 |
-
protected function is_process_running() {
|
216 |
-
if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
|
217 |
-
// Process already running.
|
218 |
-
return true;
|
219 |
-
}
|
220 |
-
|
221 |
-
return false;
|
222 |
-
}
|
223 |
-
|
224 |
-
/**
|
225 |
-
* Lock process
|
226 |
-
*
|
227 |
-
* Lock the process so that multiple instances can't run simultaneously.
|
228 |
-
* Override if applicable, but the duration should be greater than that
|
229 |
-
* defined in the time_exceeded() method.
|
230 |
-
*/
|
231 |
-
protected function lock_process() {
|
232 |
-
$this->start_time = time(); // Set start time of current process.
|
233 |
-
|
234 |
-
$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
|
235 |
-
$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
|
236 |
-
|
237 |
-
set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
|
238 |
-
}
|
239 |
-
|
240 |
-
/**
|
241 |
-
* Unlock process
|
242 |
-
*
|
243 |
-
* Unlock the process so that other instances can spawn.
|
244 |
-
*
|
245 |
-
* @return $this
|
246 |
-
*/
|
247 |
-
protected function unlock_process() {
|
248 |
-
delete_site_transient( $this->identifier . '_process_lock' );
|
249 |
-
|
250 |
-
return $this;
|
251 |
-
}
|
252 |
-
|
253 |
-
/**
|
254 |
-
* Get batch
|
255 |
-
*
|
256 |
-
* @return stdClass Return the first batch from the queue
|
257 |
-
*/
|
258 |
-
protected function get_batch() {
|
259 |
-
global $wpdb;
|
260 |
-
|
261 |
-
$table = $wpdb->options;
|
262 |
-
$column = 'option_name';
|
263 |
-
$key_column = 'option_id';
|
264 |
-
$value_column = 'option_value';
|
265 |
-
|
266 |
-
if ( is_multisite() ) {
|
267 |
-
$table = $wpdb->sitemeta;
|
268 |
-
$column = 'meta_key';
|
269 |
-
$key_column = 'meta_id';
|
270 |
-
$value_column = 'meta_value';
|
271 |
-
}
|
272 |
-
|
273 |
-
$key = $this->identifier . '_batch_%';
|
274 |
-
|
275 |
-
$query = $wpdb->get_row( $wpdb->prepare( "
|
276 |
-
SELECT *
|
277 |
-
FROM {$table}
|
278 |
-
WHERE {$column} LIKE %s
|
279 |
-
ORDER BY {$key_column} ASC
|
280 |
-
LIMIT 1
|
281 |
-
", $key ) );
|
282 |
-
|
283 |
-
$batch = new stdClass();
|
284 |
-
$batch->key = $query->$column;
|
285 |
-
$batch->data = maybe_unserialize( $query->$value_column );
|
286 |
-
|
287 |
-
return $batch;
|
288 |
-
}
|
289 |
-
|
290 |
-
/**
|
291 |
-
* Handle
|
292 |
-
*
|
293 |
-
* Pass each queue item to the task handler, while remaining
|
294 |
-
* within server memory and time limit constraints.
|
295 |
-
*/
|
296 |
-
protected function handle() {
|
297 |
-
$this->lock_process();
|
298 |
-
|
299 |
-
do {
|
300 |
-
$batch = $this->get_batch();
|
301 |
-
|
302 |
-
foreach ( $batch->data as $key => $value ) {
|
303 |
-
$task = $this->task( $value );
|
304 |
-
|
305 |
-
if ( false !== $task ) {
|
306 |
-
$batch->data[ $key ] = $task;
|
307 |
-
} else {
|
308 |
-
unset( $batch->data[ $key ] );
|
309 |
-
}
|
310 |
-
|
311 |
-
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
|
312 |
-
// Batch limits reached.
|
313 |
-
break;
|
314 |
-
}
|
315 |
-
}
|
316 |
-
|
317 |
-
// Update or delete current batch.
|
318 |
-
if ( ! empty( $batch->data ) ) {
|
319 |
-
$this->update( $batch->key, $batch->data );
|
320 |
-
} else {
|
321 |
-
$this->delete( $batch->key );
|
322 |
-
}
|
323 |
-
} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
|
324 |
-
|
325 |
-
$this->unlock_process();
|
326 |
-
|
327 |
-
// Start next batch or complete process.
|
328 |
-
if ( ! $this->is_queue_empty() ) {
|
329 |
-
$this->dispatch();
|
330 |
-
} else {
|
331 |
-
$this->complete();
|
332 |
-
}
|
333 |
-
|
334 |
-
wp_die();
|
335 |
-
}
|
336 |
-
|
337 |
-
/**
|
338 |
-
* Memory exceeded
|
339 |
-
*
|
340 |
-
* Ensures the batch process never exceeds 90%
|
341 |
-
* of the maximum WordPress memory.
|
342 |
-
*
|
343 |
-
* @return bool
|
344 |
-
*/
|
345 |
-
protected function memory_exceeded() {
|
346 |
-
$memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
|
347 |
-
$current_memory = memory_get_usage( true );
|
348 |
-
$return = false;
|
349 |
-
|
350 |
-
if ( $current_memory >= $memory_limit ) {
|
351 |
-
$return = true;
|
352 |
-
}
|
353 |
-
|
354 |
-
return apply_filters( $this->identifier . '_memory_exceeded', $return );
|
355 |
-
}
|
356 |
-
|
357 |
-
/**
|
358 |
-
* Get memory limit
|
359 |
-
*
|
360 |
-
* @return int
|
361 |
-
*/
|
362 |
-
protected function get_memory_limit() {
|
363 |
-
if ( function_exists( 'ini_get' ) ) {
|
364 |
-
$memory_limit = ini_get( 'memory_limit' );
|
365 |
-
} else {
|
366 |
-
// Sensible default.
|
367 |
-
$memory_limit = '128M';
|
368 |
-
}
|
369 |
-
|
370 |
-
if ( ! $memory_limit || -1 === $memory_limit ) {
|
371 |
-
// Unlimited, set to 32GB.
|
372 |
-
$memory_limit = '32000M';
|
373 |
-
}
|
374 |
-
|
375 |
-
return intval( $memory_limit ) * 1024 * 1024;
|
376 |
-
}
|
377 |
-
|
378 |
-
/**
|
379 |
-
* Time exceeded.
|
380 |
-
*
|
381 |
-
* Ensures the batch never exceeds a sensible time limit.
|
382 |
-
* A timeout limit of 30s is common on shared hosting.
|
383 |
-
*
|
384 |
-
* @return bool
|
385 |
-
*/
|
386 |
-
protected function time_exceeded() {
|
387 |
-
$finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
|
388 |
-
$return = false;
|
389 |
-
|
390 |
-
if ( time() >= $finish ) {
|
391 |
-
$return = true;
|
392 |
-
}
|
393 |
-
|
394 |
-
return apply_filters( $this->identifier . '_time_exceeded', $return );
|
395 |
-
}
|
396 |
-
|
397 |
-
/**
|
398 |
-
* Complete.
|
399 |
-
*
|
400 |
-
* Override if applicable, but ensure that the below actions are
|
401 |
-
* performed, or, call parent::complete().
|
402 |
-
*/
|
403 |
-
protected function complete() {
|
404 |
-
// Unschedule the cron healthcheck.
|
405 |
-
$this->clear_scheduled_event();
|
406 |
-
}
|
407 |
-
|
408 |
-
/**
|
409 |
-
* Schedule cron healthcheck
|
410 |
-
*
|
411 |
-
* @access public
|
412 |
-
* @param mixed $schedules Schedules.
|
413 |
-
* @return mixed
|
414 |
-
*/
|
415 |
-
public function schedule_cron_healthcheck( $schedules ) {
|
416 |
-
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
|
417 |
-
|
418 |
-
if ( property_exists( $this, 'cron_interval' ) ) {
|
419 |
-
$interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval_identifier );
|
420 |
-
}
|
421 |
-
|
422 |
-
// Adds every 5 minutes to the existing schedules.
|
423 |
-
$schedules[ $this->identifier . '_cron_interval' ] = array(
|
424 |
-
'interval' => MINUTE_IN_SECONDS * $interval,
|
425 |
-
'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
|
426 |
-
);
|
427 |
-
|
428 |
-
return $schedules;
|
429 |
-
}
|
430 |
-
|
431 |
-
/**
|
432 |
-
* Handle cron healthcheck
|
433 |
-
*
|
434 |
-
* Restart the background process if not already running
|
435 |
-
* and data exists in the queue.
|
436 |
-
*/
|
437 |
-
public function handle_cron_healthcheck() {
|
438 |
-
if ( $this->is_process_running() ) {
|
439 |
-
// Background process already running.
|
440 |
-
exit;
|
441 |
-
}
|
442 |
-
|
443 |
-
if ( $this->is_queue_empty() ) {
|
444 |
-
// No data to process.
|
445 |
-
$this->clear_scheduled_event();
|
446 |
-
exit;
|
447 |
-
}
|
448 |
-
|
449 |
-
$this->handle();
|
450 |
-
|
451 |
-
exit;
|
452 |
-
}
|
453 |
-
|
454 |
-
/**
|
455 |
-
* Schedule event
|
456 |
-
*/
|
457 |
-
protected function schedule_event() {
|
458 |
-
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
|
459 |
-
wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
|
460 |
-
}
|
461 |
-
}
|
462 |
-
|
463 |
-
/**
|
464 |
-
* Clear scheduled event
|
465 |
-
*/
|
466 |
-
protected function clear_scheduled_event() {
|
467 |
-
$timestamp = wp_next_scheduled( $this->cron_hook_identifier );
|
468 |
-
|
469 |
-
if ( $timestamp ) {
|
470 |
-
wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
|
471 |
-
}
|
472 |
-
}
|
473 |
-
|
474 |
-
/**
|
475 |
-
* Cancel Process
|
476 |
-
*
|
477 |
-
* Stop processing queue items, clear cronjob and delete batch.
|
478 |
-
*
|
479 |
-
*/
|
480 |
-
public function cancel_process() {
|
481 |
-
if ( ! $this->is_queue_empty() ) {
|
482 |
-
$batch = $this->get_batch();
|
483 |
-
|
484 |
-
$this->delete( $batch->key );
|
485 |
-
|
486 |
-
wp_clear_scheduled_hook( $this->cron_hook_identifier );
|
487 |
-
}
|
488 |
-
|
489 |
-
}
|
490 |
-
|
491 |
-
/**
|
492 |
-
* Task
|
493 |
-
*
|
494 |
-
* Override this method to perform any actions required on each
|
495 |
-
* queue item. Return the modified item for further processing
|
496 |
-
* in the next pass through. Or, return false to remove the
|
497 |
-
* item from the queue.
|
498 |
-
*
|
499 |
-
* @param mixed $item Queue item to iterate over.
|
500 |
-
*
|
501 |
-
* @return mixed
|
502 |
-
*/
|
503 |
-
abstract protected function task( $item );
|
504 |
-
|
505 |
-
}
|
506 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/BackgroundProcessing/wp-background-processing.php
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* WP-Background Processing
|
4 |
-
*
|
5 |
-
* @package WP-Background-Processing
|
6 |
-
*/
|
7 |
-
|
8 |
-
/*
|
9 |
-
Plugin Name: WP Background Processing
|
10 |
-
Plugin URI: https://github.com/A5hleyRich/wp-background-processing
|
11 |
-
Description: Asynchronous requests and background processing in WordPress.
|
12 |
-
Author: Delicious Brains Inc.
|
13 |
-
Version: 1.0
|
14 |
-
Author URI: https://deliciousbrains.com/
|
15 |
-
GitHub Plugin URI: https://github.com/A5hleyRich/wp-background-processing
|
16 |
-
GitHub Branch: master
|
17 |
-
*/
|
18 |
-
|
19 |
-
require_once plugin_dir_path( __FILE__ ) . 'classes/wp-async-request.php';
|
20 |
-
require_once plugin_dir_path( __FILE__ ) . 'classes/wp-background-process.php';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/FormBuilder/Components/DateTime.php
CHANGED
@@ -2,6 +2,8 @@
|
|
2 |
|
3 |
namespace FluentForm\App\Services\FormBuilder\Components;
|
4 |
|
|
|
|
|
5 |
class DateTime extends BaseComponent
|
6 |
{
|
7 |
/**
|
@@ -12,43 +14,97 @@ class DateTime extends BaseComponent
|
|
12 |
*/
|
13 |
public function compile($data, $form)
|
14 |
{
|
15 |
-
|
|
|
16 |
|
17 |
$data['attributes']['class'] = trim(
|
18 |
-
'ff-el-form-control '. $data['attributes']['class']
|
19 |
);
|
|
|
|
|
20 |
$data['attributes']['id'] = $this->makeElementId($data, $form);
|
|
|
21 |
|
22 |
$elMarkup = "<input data-type-datepicker data-format='%s' %s>";
|
23 |
|
24 |
$elMarkup = sprintf(
|
25 |
$elMarkup,
|
26 |
-
$
|
27 |
$this->buildAttributes($data['attributes'])
|
28 |
);
|
29 |
|
30 |
echo $this->buildElementMarkup($elMarkup, $data, $form);
|
31 |
}
|
32 |
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
}
|
2 |
|
3 |
namespace FluentForm\App\Services\FormBuilder\Components;
|
4 |
|
5 |
+
use FluentForm\Framework\Helpers\ArrayHelper;
|
6 |
+
|
7 |
class DateTime extends BaseComponent
|
8 |
{
|
9 |
/**
|
14 |
*/
|
15 |
public function compile($data, $form)
|
16 |
{
|
17 |
+
wp_enqueue_script('flatpickr');
|
18 |
+
wp_enqueue_style('flatpickr');
|
19 |
|
20 |
$data['attributes']['class'] = trim(
|
21 |
+
'ff-el-form-control ff-el-datepicker '. $data['attributes']['class']
|
22 |
);
|
23 |
+
$dateFormat = $data['settings']['date_format'];
|
24 |
+
|
25 |
$data['attributes']['id'] = $this->makeElementId($data, $form);
|
26 |
+
$data['attributes']['data-date_config'] = $this->getDateFormatConfigJSON($data['settings'], $form);
|
27 |
|
28 |
$elMarkup = "<input data-type-datepicker data-format='%s' %s>";
|
29 |
|
30 |
$elMarkup = sprintf(
|
31 |
$elMarkup,
|
32 |
+
$dateFormat,
|
33 |
$this->buildAttributes($data['attributes'])
|
34 |
);
|
35 |
|
36 |
echo $this->buildElementMarkup($elMarkup, $data, $form);
|
37 |
}
|
38 |
|
39 |
+
|
40 |
+
|
41 |
+
public function getAvailableDateFormats() {
|
42 |
+
$dateFormats = apply_filters('fluentform/available_date_formats', array(
|
43 |
+
'm/d/Y' => 'm/d/Y - (Ex: 04/28/2018)', // USA
|
44 |
+
'd/m/Y' => 'd/m/Y - (Ex: 28/04/2018)', // Canada, UK
|
45 |
+
'd.m.Y' => 'd.m.Y - (Ex: 28.04.2019)', // Germany
|
46 |
+
'n/j/y' => 'n/j/y - (Ex: 4/28/18)',
|
47 |
+
'm/d/y' => 'm/d/y - (Ex: 04/28/18)',
|
48 |
+
'M/d/Y' => 'M/d/Y - (Ex: Apr/28/2018)',
|
49 |
+
'y/m/d' => 'y/m/d - (Ex: 18/04/28)',
|
50 |
+
'Y-m-d' => 'Y-m-d - (Ex: 2018-04-28)',
|
51 |
+
'd-M-y' => 'd-M-y - (Ex: 28-Apr-18)',
|
52 |
+
'm/d/Y h:i K' => 'm/d/Y h:i K - (Ex: 04/28/2018 08:55 PM)', // USA
|
53 |
+
'm/d/Y H:i' => 'm/d/Y H:i - (Ex: 04/28/2018 20:55)', // USA
|
54 |
+
'd/m/Y h:i K' => 'd/m/Y h:i K - (Ex: 28/04/2018 08:55 PM)', // Canada, UK
|
55 |
+
'd/m/Y H:i' => 'd/m/Y H:i - (Ex: 28/04/2018 20:55)', // Canada, UK
|
56 |
+
'd.m.Y h:i K' => 'd.m.Y h:i K - (Ex: 28.04.2019 08:55 PM)', // Germany
|
57 |
+
'd.m.Y H:i' => 'd.m.Y H:i - (Ex: 28.04.2019 20:55)', // Germany
|
58 |
+
'h:i K' => 'h:i K (Only Time Ex: 08:55 PM)',
|
59 |
+
'H:i' => 'H:i (Only Time Ex: 20:55)',
|
60 |
+
));
|
61 |
+
|
62 |
+
$formatted = [];
|
63 |
+
foreach ($dateFormats as $format => $label) {
|
64 |
+
$formatted[] = [
|
65 |
+
'label' => $label,
|
66 |
+
'value' => $format
|
67 |
+
];
|
68 |
+
}
|
69 |
+
return $formatted;
|
70 |
+
}
|
71 |
+
|
72 |
+
private function getDateFormatConfigJSON($settings, $form)
|
73 |
+
{
|
74 |
+
$dateFormat = ArrayHelper::get($settings, 'date_format');
|
75 |
+
if(!$dateFormat) {
|
76 |
+
$dateFormat = 'm/d/Y';
|
77 |
+
}
|
78 |
+
|
79 |
+
$config = apply_filters('fluentform/frontend_date_format', array(
|
80 |
+
'dateFormat' => $dateFormat,
|
81 |
+
'enableTime' => $this->hasTime($dateFormat),
|
82 |
+
'noCalendar' => !$this->hasDate($dateFormat),
|
83 |
+
), $settings, $form);
|
84 |
+
|
85 |
+
return json_encode($config, JSON_FORCE_OBJECT);
|
86 |
+
|
87 |
+
}
|
88 |
+
|
89 |
+
private function hasTime($string)
|
90 |
+
{
|
91 |
+
$timeStrings = ['H', 'h', 'G', 'i', 'S', 's', 'K'];
|
92 |
+
foreach ($timeStrings as $timeString) {
|
93 |
+
if (strpos($string, $timeString) != false) {
|
94 |
+
return true;
|
95 |
+
}
|
96 |
+
}
|
97 |
+
return false;
|
98 |
+
}
|
99 |
+
|
100 |
+
private function hasDate($string)
|
101 |
+
{
|
102 |
+
$dateStrings = ['d', 'D', 'l', 'j', 'J', 'w', 'W', 'F', 'm', 'n', 'M', 'U', 'Y', 'y', 'Z'];
|
103 |
+
foreach ($dateStrings as $dateString) {
|
104 |
+
if (strpos($string, $dateString) != false) {
|
105 |
+
return 'true';
|
106 |
+
}
|
107 |
+
}
|
108 |
+
return false;
|
109 |
+
}
|
110 |
}
|
app/Services/FormBuilder/Components/Select.php
CHANGED
@@ -16,6 +16,9 @@ class Select extends BaseComponent
|
|
16 |
|
17 |
if (@$data['attributes']['multiple']) {
|
18 |
$data['attributes']['name'] = $data['attributes']['name'].'[]';
|
|
|
|
|
|
|
19 |
}
|
20 |
|
21 |
$data['attributes']['class'] = trim('ff-el-form-control ' . $data['attributes']['class']);
|
16 |
|
17 |
if (@$data['attributes']['multiple']) {
|
18 |
$data['attributes']['name'] = $data['attributes']['name'].'[]';
|
19 |
+
wp_enqueue_script('selectWoo');
|
20 |
+
wp_enqueue_style('select2');
|
21 |
+
$data['attributes']['class'] .= ' ff_has_multi_select';
|
22 |
}
|
23 |
|
24 |
$data['attributes']['class'] = trim('ff-el-form-control ' . $data['attributes']['class']);
|
app/Services/FormBuilder/DefaultElements.php
CHANGED
@@ -27,13 +27,13 @@ return array(
|
|
27 |
),
|
28 |
'settings' => array (
|
29 |
'container_class' => '',
|
30 |
-
'label' => 'First Name',
|
31 |
'help_message' => '',
|
32 |
'visible' => true,
|
33 |
'validation_rules' => array (
|
34 |
'required' => array (
|
35 |
'value' => false,
|
36 |
-
'message' => 'This field is required',
|
37 |
),
|
38 |
),
|
39 |
'conditional_logics' => array (),
|
@@ -55,14 +55,14 @@ return array(
|
|
55 |
),
|
56 |
'settings' => array (
|
57 |
'container_class' => '',
|
58 |
-
'label' => 'Middle Name',
|
59 |
'help_message' => '',
|
60 |
'error_message' => '',
|
61 |
'visible' => false,
|
62 |
'validation_rules' => array (
|
63 |
'required' => array (
|
64 |
'value' => false,
|
65 |
-
'message' => 'This field is required',
|
66 |
),
|
67 |
),
|
68 |
'conditional_logics' => array (),
|
@@ -84,14 +84,14 @@ return array(
|
|
84 |
),
|
85 |
'settings' => array (
|
86 |
'container_class' => '',
|
87 |
-
'label' => 'Last Name',
|
88 |
'help_message' => '',
|
89 |
'error_message' => '',
|
90 |
'visible' => true,
|
91 |
'validation_rules' => array (
|
92 |
'required' => array (
|
93 |
'value' => false,
|
94 |
-
'message' => 'This field is required',
|
95 |
),
|
96 |
),
|
97 |
'conditional_logics' => array (),
|
@@ -121,24 +121,24 @@ return array(
|
|
121 |
),
|
122 |
'settings' => array (
|
123 |
'container_class' => '',
|
124 |
-
'label' => 'Email',
|
125 |
'label_placement' => '',
|
126 |
'help_message' => '',
|
127 |
'admin_field_label' => '',
|
128 |
'validation_rules' => array (
|
129 |
'required' => array (
|
130 |
'value' => false,
|
131 |
-
'message' => 'This field is required',
|
132 |
),
|
133 |
'email' => array (
|
134 |
'value' => true,
|
135 |
-
'message' => 'This field must contain a valid email',
|
136 |
),
|
137 |
),
|
138 |
'conditional_logics' => array (),
|
139 |
),
|
140 |
'editor_options' => array (
|
141 |
-
'title' => 'Email Address',
|
142 |
'icon_class' => 'icon-envelope-o',
|
143 |
'template' => 'inputText'
|
144 |
),
|
@@ -155,20 +155,20 @@ return array(
|
|
155 |
),
|
156 |
'settings' => array (
|
157 |
'container_class' => '',
|
158 |
-
'label' => 'Text Input',
|
159 |
'label_placement' => '',
|
160 |
'admin_field_label' => '',
|
161 |
'help_message' => '',
|
162 |
'validation_rules' => array (
|
163 |
'required' => array (
|
164 |
'value' => false,
|
165 |
-
'message' => 'This field is required',
|
166 |
),
|
167 |
),
|
168 |
'conditional_logics' => array (),
|
169 |
),
|
170 |
'editor_options' => array (
|
171 |
-
'title' => 'Simple Text',
|
172 |
'icon_class' => 'icon-text-width',
|
173 |
'template' => 'inputText'
|
174 |
)
|
@@ -186,7 +186,7 @@ return array(
|
|
186 |
),
|
187 |
'settings' => array (
|
188 |
'container_class' => '',
|
189 |
-
'label' => 'Mask Input',
|
190 |
'label_placement' => '',
|
191 |
'admin_field_label' => '',
|
192 |
'help_message' => '',
|
@@ -194,13 +194,13 @@ return array(
|
|
194 |
'validation_rules' => array (
|
195 |
'required' => array (
|
196 |
'value' => false,
|
197 |
-
'message' => 'This field is required',
|
198 |
),
|
199 |
),
|
200 |
'conditional_logics' => array (),
|
201 |
),
|
202 |
'editor_options' => array (
|
203 |
-
'title' => 'Mask Input',
|
204 |
'icon_class' => 'icon-keyboard-o',
|
205 |
'template' => 'inputText'
|
206 |
)
|
@@ -219,20 +219,20 @@ return array(
|
|
219 |
),
|
220 |
'settings' => array (
|
221 |
'container_class' => '',
|
222 |
-
'label' => 'Textarea',
|
223 |
'admin_field_label' => '',
|
224 |
'label_placement' => '',
|
225 |
'help_message' => '',
|
226 |
'validation_rules' => array (
|
227 |
'required' => array (
|
228 |
'value' => false,
|
229 |
-
'message' => 'This field is required',
|
230 |
),
|
231 |
),
|
232 |
'conditional_logics' => array ()
|
233 |
),
|
234 |
'editor_options' => array (
|
235 |
-
'title' => 'Text Area',
|
236 |
'icon_class' => 'icon-paragraph',
|
237 |
'template' => 'inputTextarea'
|
238 |
),
|
@@ -247,7 +247,7 @@ return array(
|
|
247 |
'data-type' => 'address-element'
|
248 |
),
|
249 |
'settings' => array (
|
250 |
-
'label' => 'Address',
|
251 |
'admin_field_label' => '',
|
252 |
'conditional_logics' => array (),
|
253 |
),
|
@@ -264,14 +264,14 @@ return array(
|
|
264 |
),
|
265 |
'settings' => array (
|
266 |
'container_class' => '',
|
267 |
-
'label' => 'Address Line 1',
|
268 |
'admin_field_label' => '',
|
269 |
'help_message' => '',
|
270 |
'visible' => true,
|
271 |
'validation_rules' => array (
|
272 |
'required' => array (
|
273 |
'value' => false,
|
274 |
-
'message' => 'This field is required',
|
275 |
),
|
276 |
),
|
277 |
'conditional_logics' => array (),
|
@@ -292,14 +292,14 @@ return array(
|
|
292 |
),
|
293 |
'settings' => array (
|
294 |
'container_class' => '',
|
295 |
-
'label' => 'Address Line 2',
|
296 |
'admin_field_label' => '',
|
297 |
'help_message' => '',
|
298 |
'visible' => true,
|
299 |
'validation_rules' => array (
|
300 |
'required' => array (
|
301 |
'value' => false,
|
302 |
-
'message' => 'This field is required',
|
303 |
),
|
304 |
),
|
305 |
'conditional_logics' => array (),
|
@@ -320,7 +320,7 @@ return array(
|
|
320 |
),
|
321 |
'settings' => array (
|
322 |
'container_class' => '',
|
323 |
-
'label' => 'City',
|
324 |
'admin_field_label' => '',
|
325 |
'help_message' => '',
|
326 |
'error_message' => '',
|
@@ -328,7 +328,7 @@ return array(
|
|
328 |
'validation_rules' => array (
|
329 |
'required' => array (
|
330 |
'value' => false,
|
331 |
-
'message' => 'This field is required',
|
332 |
),
|
333 |
),
|
334 |
'conditional_logics' => array (),
|
@@ -349,7 +349,7 @@ return array(
|
|
349 |
),
|
350 |
'settings' => array (
|
351 |
'container_class' => '',
|
352 |
-
'label' => 'State',
|
353 |
'admin_field_label' => '',
|
354 |
'help_message' => '',
|
355 |
'error_message' => '',
|
@@ -357,7 +357,7 @@ return array(
|
|
357 |
'validation_rules' => array (
|
358 |
'required' => array (
|
359 |
'value' => false,
|
360 |
-
'message' => 'This field is required',
|
361 |
),
|
362 |
),
|
363 |
'conditional_logics' => array (),
|
@@ -379,7 +379,7 @@ return array(
|
|
379 |
),
|
380 |
'settings' => array (
|
381 |
'container_class' => '',
|
382 |
-
'label' => 'Zip Code',
|
383 |
'admin_field_label' => '',
|
384 |
'help_message' => '',
|
385 |
'error_message' => '',
|
@@ -387,7 +387,7 @@ return array(
|
|
387 |
'validation_rules' => array (
|
388 |
'required' => array (
|
389 |
'value' => false,
|
390 |
-
'message' => 'This field is required',
|
391 |
),
|
392 |
),
|
393 |
'conditional_logics' => array (),
|
@@ -408,7 +408,7 @@ return array(
|
|
408 |
),
|
409 |
'settings' => array (
|
410 |
'container_class' => '',
|
411 |
-
'label' => 'Country',
|
412 |
'admin_field_label' => '',
|
413 |
'help_message' => '',
|
414 |
'error_message' => '',
|
@@ -416,7 +416,7 @@ return array(
|
|
416 |
'validation_rules' => array (
|
417 |
'required' => array (
|
418 |
'value' => false,
|
419 |
-
'message' => 'This field is required',
|
420 |
),
|
421 |
),
|
422 |
'country_list' => array (
|
@@ -439,7 +439,7 @@ return array(
|
|
439 |
),
|
440 |
),
|
441 |
'editor_options' => array (
|
442 |
-
'title' => 'Address Fields',
|
443 |
'element' => 'address-fields',
|
444 |
'icon_class' => 'icon-credit-card',
|
445 |
'template' => 'addressFields'
|
@@ -457,14 +457,14 @@ return array(
|
|
457 |
),
|
458 |
'settings' => array (
|
459 |
'container_class' => '',
|
460 |
-
'label' => 'Country',
|
461 |
'admin_field_label' => '',
|
462 |
'label_placement' => '',
|
463 |
'help_message' => '',
|
464 |
'validation_rules' => array (
|
465 |
'required' => array (
|
466 |
'value' => false,
|
467 |
-
'message' => 'This field is required',
|
468 |
),
|
469 |
),
|
470 |
'country_list' => array (
|
@@ -479,7 +479,7 @@ return array(
|
|
479 |
'US' => 'United States of America',
|
480 |
),
|
481 |
'editor_options' => array (
|
482 |
-
'title' => 'Country List',
|
483 |
'element' => 'country-list',
|
484 |
'icon_class' => 'icon-globe',
|
485 |
'template' => 'selectCountry'
|
@@ -498,32 +498,32 @@ return array(
|
|
498 |
),
|
499 |
'settings' => array (
|
500 |
'container_class' => '',
|
501 |
-
'label' => 'Numeric Field',
|
502 |
'admin_field_label' => 'Numeric Field',
|
503 |
'label_placement' => '',
|
504 |
'help_message' => '',
|
505 |
'validation_rules' => array (
|
506 |
'required' => array (
|
507 |
'value' => false,
|
508 |
-
'message' => 'This field is required',
|
509 |
),
|
510 |
'numeric' => array (
|
511 |
'value' => true,
|
512 |
-
'message' => 'This field must contain numeric value',
|
513 |
),
|
514 |
'min' => array (
|
515 |
'value' => '',
|
516 |
-
'message' => 'Minimum value is ',
|
517 |
),
|
518 |
'max' => array (
|
519 |
'value' => '',
|
520 |
-
'message' => 'Maximum value is ',
|
521 |
),
|
522 |
),
|
523 |
'conditional_logics' => array (),
|
524 |
),
|
525 |
'editor_options' => array (
|
526 |
-
'title' => 'Numeric Field',
|
527 |
'icon_class' => 'icon-slack',
|
528 |
'template' => 'inputText'
|
529 |
),
|
@@ -538,7 +538,7 @@ return array(
|
|
538 |
'class' => '',
|
539 |
),
|
540 |
'settings' => array (
|
541 |
-
'label' => 'Dropdown',
|
542 |
'admin_field_label' => '',
|
543 |
'help_message' => '',
|
544 |
'container_class' => '',
|
@@ -547,17 +547,17 @@ return array(
|
|
547 |
'validation_rules' => array (
|
548 |
'required' => array (
|
549 |
'value' => false,
|
550 |
-
'message' => 'This field is required',
|
551 |
),
|
552 |
),
|
553 |
'conditional_logics' =>array (),
|
554 |
),
|
555 |
'options' => array (
|
556 |
-
'
|
557 |
-
'
|
558 |
),
|
559 |
'editor_options' => array (
|
560 |
-
'title' => 'Dropdown',
|
561 |
'icon_class' => 'icon-caret-square-o-down',
|
562 |
'element' => 'select',
|
563 |
'template' => 'select'
|
@@ -573,7 +573,7 @@ return array(
|
|
573 |
),
|
574 |
'settings' => array (
|
575 |
'container_class' => '',
|
576 |
-
'label' => 'Radio Field',
|
577 |
'admin_field_label' => '',
|
578 |
'label_placement' => '',
|
579 |
'display_type' => '',
|
@@ -581,7 +581,7 @@ return array(
|
|
581 |
'validation_rules' => array (
|
582 |
'required' => array (
|
583 |
'value' => false,
|
584 |
-
'message' => 'This field is required',
|
585 |
),
|
586 |
),
|
587 |
'conditional_logics' => array (),
|
@@ -591,7 +591,7 @@ return array(
|
|
591 |
'no' => 'No',
|
592 |
),
|
593 |
'editor_options' => array (
|
594 |
-
'title' => 'Radio Button',
|
595 |
'icon_class' => 'icon-dot-circle-o',
|
596 |
'element' => 'input-radio',
|
597 |
'template' => 'inputRadio'
|
@@ -607,7 +607,7 @@ return array(
|
|
607 |
),
|
608 |
'settings' => array (
|
609 |
'container_class' => '',
|
610 |
-
'label' => 'Checkbox Field',
|
611 |
'admin_field_label' => '',
|
612 |
'label_placement' => '',
|
613 |
'display_type' => '',
|
@@ -615,7 +615,7 @@ return array(
|
|
615 |
'validation_rules' => array (
|
616 |
'required' => array (
|
617 |
'value' => false,
|
618 |
-
'message' => 'This field is required',
|
619 |
),
|
620 |
),
|
621 |
'conditional_logics' => array (),
|
@@ -626,7 +626,7 @@ return array(
|
|
626 |
'item_3' => 'Item 3'
|
627 |
),
|
628 |
'editor_options' => array (
|
629 |
-
'title' => 'Check Box',
|
630 |
'icon_class' => 'icon-check-square-o',
|
631 |
'template' => 'inputCheckbox'
|
632 |
),
|
@@ -644,23 +644,23 @@ return array(
|
|
644 |
'settings' => array (
|
645 |
'help_message' => '',
|
646 |
'container_class' => '',
|
647 |
-
'label' => 'Multiselect',
|
648 |
'admin_field_label' => '',
|
649 |
'label_placement' => '',
|
650 |
'validation_rules' => array (
|
651 |
'required' => array (
|
652 |
'value' => false,
|
653 |
-
'message' => 'This field is required',
|
654 |
),
|
655 |
),
|
656 |
'conditional_logics' => array (),
|
657 |
),
|
658 |
'options' => array (
|
659 |
-
'
|
660 |
-
'
|
661 |
),
|
662 |
'editor_options' => array (
|
663 |
-
'title' => 'Multiple Choice',
|
664 |
'icon_class' => 'icon-list-ul',
|
665 |
'element' => 'select',
|
666 |
'template' => 'select'
|
@@ -678,24 +678,24 @@ return array(
|
|
678 |
),
|
679 |
'settings' => array (
|
680 |
'container_class' => '',
|
681 |
-
'label' => 'URL',
|
682 |
'admin_field_label' => '',
|
683 |
'label_placement' => '',
|
684 |
'help_message' => '',
|
685 |
'validation_rules' => array (
|
686 |
'required' => array (
|
687 |
'value' => false,
|
688 |
-
'message' => 'This field is required',
|
689 |
),
|
690 |
'url' => array (
|
691 |
'value' => true,
|
692 |
-
'message' => 'This field must contain a valid url',
|
693 |
),
|
694 |
),
|
695 |
'conditional_logics' => array (),
|
696 |
),
|
697 |
'editor_options' => array (
|
698 |
-
'title' => 'Website URL',
|
699 |
'icon_class' => 'icon-link',
|
700 |
'template' => 'inputText'
|
701 |
)
|
@@ -713,20 +713,20 @@ return array(
|
|
713 |
),
|
714 |
'settings' => array (
|
715 |
'container_class' => '',
|
716 |
-
'label' => 'Password',
|
717 |
'admin_field_label' => '',
|
718 |
'label_placement' => '',
|
719 |
'help_message' => '',
|
720 |
'validation_rules' => array (
|
721 |
'required' => array (
|
722 |
'value' => false,
|
723 |
-
'message' => 'This field is required',
|
724 |
),
|
725 |
),
|
726 |
'conditional_logics' => array (),
|
727 |
),
|
728 |
'editor_options' => array (
|
729 |
-
'title' => 'Password Field',
|
730 |
'icon_class' => 'icon-lock',
|
731 |
'template' => 'inputText'
|
732 |
),
|
@@ -744,7 +744,7 @@ return array(
|
|
744 |
),
|
745 |
'settings' => array (
|
746 |
'container_class' => '',
|
747 |
-
'label' => 'Date / Time',
|
748 |
'admin_field_label' => '',
|
749 |
'label_placement' => '',
|
750 |
'date_format' => 'd/m/Y',
|
@@ -753,13 +753,13 @@ return array(
|
|
753 |
'validation_rules' => array (
|
754 |
'required' => array (
|
755 |
'value' => false,
|
756 |
-
'message' => 'This field is required',
|
757 |
)
|
758 |
),
|
759 |
'conditional_logics' => array (),
|
760 |
),
|
761 |
'editor_options' => array (
|
762 |
-
'title' => 'Time & Date',
|
763 |
'icon_class' => 'icon-calendar-o',
|
764 |
'template' => 'inputText'
|
765 |
),
|
@@ -776,7 +776,7 @@ return array(
|
|
776 |
),
|
777 |
'settings' => array (
|
778 |
'container_class' => '',
|
779 |
-
'label' => 'File Upload',
|
780 |
'admin_field_label' => '',
|
781 |
'label_placement' => '',
|
782 |
'btn_text' => 'Choose File',
|
@@ -784,26 +784,26 @@ return array(
|
|
784 |
'validation_rules' => array (
|
785 |
'required' => array (
|
786 |
'value' => false,
|
787 |
-
'message' => 'This field is required',
|
788 |
),
|
789 |
'max_file_size' => array (
|
790 |
'value' => 1048576,
|
791 |
'_valueFrom' => 'MB',
|
792 |
-
'message' => 'Maximum file size limit'
|
793 |
),
|
794 |
'max_file_count' => array (
|
795 |
'value' => 1,
|
796 |
-
'message' => 'Max file count'
|
797 |
),
|
798 |
'allowed_file_types' => array (
|
799 |
'value' => array(),
|
800 |
-
'message' => 'allowed_file_types'
|
801 |
)
|
802 |
),
|
803 |
'conditional_logics' => array (),
|
804 |
),
|
805 |
'editor_options' => array (
|
806 |
-
'title' => 'File Upload',
|
807 |
'icon_class' => 'icon-upload',
|
808 |
'template' => 'inputFile'
|
809 |
),
|
@@ -821,7 +821,7 @@ return array(
|
|
821 |
),
|
822 |
'settings' => array (
|
823 |
'container_class' => '',
|
824 |
-
'label' => 'Image Upload',
|
825 |
'admin_field_label' => '',
|
826 |
'label_placement' => '',
|
827 |
'btn_text' => 'Choose File',
|
@@ -829,26 +829,26 @@ return array(
|
|
829 |
'validation_rules' => array (
|
830 |
'required' => array (
|
831 |
'value' => false,
|
832 |
-
'message' => 'This field is required',
|
833 |
),
|
834 |
'max_file_size' => array (
|
835 |
'value' => 1048576,
|
836 |
'_valueFrom' => 'MB',
|
837 |
-
'message' => 'Maximum file size limit'
|
838 |
),
|
839 |
'max_file_count' => array (
|
840 |
'value' => 1,
|
841 |
-
'message' => 'Max file count'
|
842 |
),
|
843 |
'allowed_image_types' => array (
|
844 |
'value' => array(),
|
845 |
-
'message' => 'Allowed image size does not match'
|
846 |
)
|
847 |
),
|
848 |
'conditional_logics' => array (),
|
849 |
),
|
850 |
'editor_options' => array (
|
851 |
-
'title' => 'Image Upload',
|
852 |
'icon_class' => 'icon-picture-o',
|
853 |
'template' => 'inputFile'
|
854 |
),
|
@@ -861,7 +861,7 @@ return array(
|
|
861 |
'data-type' => 'repeat-element'
|
862 |
),
|
863 |
'settings' => array (
|
864 |
-
'label' => 'Repeat Fields',
|
865 |
'admin_field_label' => '',
|
866 |
'container_class' => '',
|
867 |
'label_placement' => '',
|
@@ -878,12 +878,12 @@ return array(
|
|
878 |
'placeholder' => '',
|
879 |
),
|
880 |
'settings' => array (
|
881 |
-
'label' => 'Column 1',
|
882 |
'help_message' => '',
|
883 |
'validation_rules' => array (
|
884 |
'required' => array (
|
885 |
'value' => false,
|
886 |
-
'message' => 'This field is required',
|
887 |
)
|
888 |
)
|
889 |
),
|
@@ -891,7 +891,7 @@ return array(
|
|
891 |
)
|
892 |
),
|
893 |
'editor_options' => array (
|
894 |
-
'title' => 'Repeat Field',
|
895 |
'icon_class' => 'icon-text-width',
|
896 |
'template' => 'repeatFields'
|
897 |
),
|
@@ -938,7 +938,7 @@ return array(
|
|
938 |
'admin_field_label' => ''
|
939 |
),
|
940 |
'editor_options' => array (
|
941 |
-
'title' => 'Hidden Field',
|
942 |
'icon_class' => 'icon-eye-slash',
|
943 |
'template' => 'inputHidden',
|
944 |
),
|
@@ -951,13 +951,13 @@ return array(
|
|
951 |
'class' => '',
|
952 |
),
|
953 |
'settings' => array(
|
954 |
-
'label' => 'Section Break',
|
955 |
-
'description' => 'Some description about this section',
|
956 |
'align' => 'left',
|
957 |
'conditional_logics' => array(),
|
958 |
),
|
959 |
'editor_options' => array(
|
960 |
-
'title' => 'Section Break',
|
961 |
'icon_class' => 'icon-puzzle-piece',
|
962 |
'template' => 'sectionBreak',
|
963 |
),
|
@@ -972,7 +972,7 @@ return array(
|
|
972 |
'validation_rules' => array(),
|
973 |
),
|
974 |
'editor_options' => array(
|
975 |
-
'title' => 'reCaptcha',
|
976 |
'icon_class' => 'icon-qrcode',
|
977 |
'why_disabled_modal' => 'recaptcha',
|
978 |
'template' => 'recaptcha',
|
@@ -987,7 +987,7 @@ return array(
|
|
987 |
'conditional_logics' => array()
|
988 |
),
|
989 |
'editor_options' => array(
|
990 |
-
'title' => 'Custom HTML',
|
991 |
'icon_class' => 'icon-code',
|
992 |
'template' => 'customHTML',
|
993 |
)
|
@@ -1004,7 +1004,7 @@ return array(
|
|
1004 |
'conditional_logics' => array(),
|
1005 |
),
|
1006 |
'editor_options' => array(
|
1007 |
-
'title' => 'Shortcode',
|
1008 |
'icon_class' => 'icon-certificate',
|
1009 |
'template' => 'shortcode',
|
1010 |
)
|
@@ -1021,18 +1021,18 @@ return array(
|
|
1021 |
'settings' => array(
|
1022 |
'tnc_html' => 'I have read and agree to the <a href="#">Terms and Conditions</a> and <a href="#">Privacy Policy</a>',
|
1023 |
'has_checkbox' => true,
|
1024 |
-
'admin_field_label' => 'Terms and Conditions',
|
1025 |
'container_class' => '',
|
1026 |
'validation_rules' => array (
|
1027 |
'required' => array (
|
1028 |
'value' => false,
|
1029 |
-
'message' => 'This field is required',
|
1030 |
),
|
1031 |
),
|
1032 |
'conditional_logics' => array(),
|
1033 |
),
|
1034 |
'editor_options' => array(
|
1035 |
-
'title' => 'Terms & Conditions',
|
1036 |
'icon_class' => 'icon-check-square-o',
|
1037 |
'template' => 'termsCheckbox'
|
1038 |
),
|
@@ -1049,7 +1049,7 @@ return array(
|
|
1049 |
'conditional_logics' => array(),
|
1050 |
),
|
1051 |
'editor_options' => array(
|
1052 |
-
'title' => 'Action Hook',
|
1053 |
'icon_class' => 'icon-paragraph',
|
1054 |
'template' => 'actionHook'
|
1055 |
)
|
@@ -1064,17 +1064,17 @@ return array(
|
|
1064 |
'settings' => [
|
1065 |
'prev_btn' => [
|
1066 |
'type' => 'default',
|
1067 |
-
'text' => 'Previous',
|
1068 |
'img_url' => '',
|
1069 |
],
|
1070 |
'next_btn' => [
|
1071 |
'type' => 'default',
|
1072 |
-
'text' => 'Next',
|
1073 |
'img_url' => '',
|
1074 |
],
|
1075 |
],
|
1076 |
'editor_options' => [
|
1077 |
-
'title' => 'Form Step',
|
1078 |
'icon_class' => 'icon-step-forward',
|
1079 |
'template' => 'formStep',
|
1080 |
],
|
@@ -1088,7 +1088,7 @@ return array(
|
|
1088 |
'name' => 'ratings',
|
1089 |
),
|
1090 |
'settings' => array (
|
1091 |
-
'label' => 'Ratings',
|
1092 |
'show_text' => false,
|
1093 |
'help_message' => '',
|
1094 |
'label_placement' => '',
|
@@ -1098,19 +1098,19 @@ return array(
|
|
1098 |
'validation_rules' => array (
|
1099 |
'required' => array (
|
1100 |
'value' => false,
|
1101 |
-
'message' => 'This field is required',
|
1102 |
),
|
1103 |
),
|
1104 |
),
|
1105 |
'options' => array (
|
1106 |
-
'1' => 'Nice',
|
1107 |
-
'2' => 'Good',
|
1108 |
-
'3' => 'Very Good',
|
1109 |
-
'4' => 'Awesome',
|
1110 |
-
'5' => 'Amazing',
|
1111 |
),
|
1112 |
'editor_options' => array (
|
1113 |
-
'title' => 'Ratings',
|
1114 |
'icon_class' => 'icon-eye-slash',
|
1115 |
'template' => 'ratings',
|
1116 |
),
|
@@ -1125,14 +1125,14 @@ return array(
|
|
1125 |
'settings' => array (
|
1126 |
'tabular_field_type' => 'checkbox',
|
1127 |
'container_class' => '',
|
1128 |
-
'label' => 'Checkbox Grid',
|
1129 |
'admin_field_label' => '',
|
1130 |
'label_placement' => '',
|
1131 |
'help_message' => '',
|
1132 |
'validation_rules' => array (
|
1133 |
'required' => array (
|
1134 |
'value' => false,
|
1135 |
-
'message' => 'This field is required',
|
1136 |
'per_row' => false,
|
1137 |
),
|
1138 |
),
|
@@ -1146,7 +1146,7 @@ return array(
|
|
1146 |
'selected_grids' => array ()
|
1147 |
),
|
1148 |
'editor_options' => array (
|
1149 |
-
'title' => 'Checkable Grid',
|
1150 |
'icon_class' => 'icon-dot-circle-o',
|
1151 |
'template' => 'checkableGrids'
|
1152 |
),
|
@@ -1161,21 +1161,21 @@ return array(
|
|
1161 |
'class' => '',
|
1162 |
),
|
1163 |
'settings' => array(
|
1164 |
-
'label' => 'GDPR Agreement',
|
1165 |
-
'tnc_html' => 'I consent to having this website store my submitted information so they can respond to my inquiry',
|
1166 |
-
'admin_field_label' => 'GDPR Agreement',
|
1167 |
'has_checkbox' => true,
|
1168 |
'container_class' => '',
|
1169 |
'validation_rules' => array (
|
1170 |
'required' => array (
|
1171 |
'value' => true,
|
1172 |
-
'message' => 'This field is required',
|
1173 |
),
|
1174 |
),
|
1175 |
'conditional_logics' => array(),
|
1176 |
),
|
1177 |
'editor_options' => array(
|
1178 |
-
'title' => 'GDPR Agreement',
|
1179 |
'icon_class' => 'icon-check-square-o',
|
1180 |
'template' => 'termsCheckbox'
|
1181 |
),
|
@@ -1193,7 +1193,7 @@ return array(
|
|
1193 |
),
|
1194 |
'editor_options' =>
|
1195 |
array(
|
1196 |
-
'title' => 'Two Column Container',
|
1197 |
'icon_class' => 'icon-columns',
|
1198 |
),
|
1199 |
),
|
@@ -1208,7 +1208,7 @@ return array(
|
|
1208 |
array( 'fields' => array() ),
|
1209 |
),
|
1210 |
'editor_options' => array(
|
1211 |
-
'title' => 'Three Column Container',
|
1212 |
'icon_class' => 'icon-columns',
|
1213 |
),
|
1214 |
)
|
@@ -1219,7 +1219,7 @@ return array(
|
|
1219 |
'index' => 17,
|
1220 |
'element' => 'product',
|
1221 |
'settings' => array(
|
1222 |
-
'label' => 'Product Name',
|
1223 |
'description' => '',
|
1224 |
'product_field_types' => '',
|
1225 |
'product_type' => 'single',
|
@@ -1231,7 +1231,7 @@ return array(
|
|
1231 |
'validation_rules' => array (
|
1232 |
'required' => array (
|
1233 |
'value' => false,
|
1234 |
-
'message' => 'This field is required',
|
1235 |
),
|
1236 |
),
|
1237 |
'conditional_logics' => array (),
|
@@ -1239,15 +1239,15 @@ return array(
|
|
1239 |
'field_types' => array(
|
1240 |
array(
|
1241 |
'value' => 'single',
|
1242 |
-
'label' => 'Single Product'
|
1243 |
),
|
1244 |
array(
|
1245 |
'value' => 'dropdown',
|
1246 |
-
'label' => 'Drop Down'
|
1247 |
),
|
1248 |
array(
|
1249 |
'value' => 'radio',
|
1250 |
-
'label' => 'Radio Buttons'
|
1251 |
)
|
1252 |
),
|
1253 |
'grid_columns' => array (
|
@@ -1267,4 +1267,4 @@ return array(
|
|
1267 |
),
|
1268 |
),
|
1269 |
),
|
1270 |
-
);
|
27 |
),
|
28 |
'settings' => array (
|
29 |
'container_class' => '',
|
30 |
+
'label' => __('First Name', 'fluentform'),
|
31 |
'help_message' => '',
|
32 |
'visible' => true,
|
33 |
'validation_rules' => array (
|
34 |
'required' => array (
|
35 |
'value' => false,
|
36 |
+
'message' => __('This field is required', 'fluentform'),
|
37 |
),
|
38 |
),
|
39 |
'conditional_logics' => array (),
|
55 |
),
|
56 |
'settings' => array (
|
57 |
'container_class' => '',
|
58 |
+
'label' => __('Middle Name', 'fluentform'),
|
59 |
'help_message' => '',
|
60 |
'error_message' => '',
|
61 |
'visible' => false,
|
62 |
'validation_rules' => array (
|
63 |
'required' => array (
|
64 |
'value' => false,
|
65 |
+
'message' => __('This field is required', 'fluentform'),
|
66 |
),
|
67 |
),
|
68 |
'conditional_logics' => array (),
|
84 |
),
|
85 |
'settings' => array (
|
86 |
'container_class' => '',
|
87 |
+
'label' => __('Last Name', 'fluentform'),
|
88 |
'help_message' => '',
|
89 |
'error_message' => '',
|
90 |
'visible' => true,
|
91 |
'validation_rules' => array (
|
92 |
'required' => array (
|
93 |
'value' => false,
|
94 |
+
'message' => __('This field is required', 'fluentform'),
|
95 |
),
|
96 |
),
|
97 |
'conditional_logics' => array (),
|
121 |
),
|
122 |
'settings' => array (
|
123 |
'container_class' => '',
|
124 |
+
'label' => __('Email', 'fluentform'),
|
125 |
'label_placement' => '',
|
126 |
'help_message' => '',
|
127 |
'admin_field_label' => '',
|
128 |
'validation_rules' => array (
|
129 |
'required' => array (
|
130 |
'value' => false,
|
131 |
+
'message' => __('This field is required', 'fluentform'),
|
132 |
),
|
133 |
'email' => array (
|
134 |
'value' => true,
|
135 |
+
'message' => __('This field must contain a valid email', 'fluentform'),
|
136 |
),
|
137 |
),
|
138 |
'conditional_logics' => array (),
|
139 |
),
|
140 |
'editor_options' => array (
|
141 |
+
'title' => __('Email Address', 'fluentform'),
|
142 |
'icon_class' => 'icon-envelope-o',
|
143 |
'template' => 'inputText'
|
144 |
),
|
155 |
),
|
156 |
'settings' => array (
|
157 |
'container_class' => '',
|
158 |
+
'label' => __('Text Input', 'fluentform'),
|
159 |
'label_placement' => '',
|
160 |
'admin_field_label' => '',
|
161 |
'help_message' => '',
|
162 |
'validation_rules' => array (
|
163 |
'required' => array (
|
164 |
'value' => false,
|
165 |
+
'message' => __('This field is required', 'fluentform'),
|
166 |
),
|
167 |
),
|
168 |
'conditional_logics' => array (),
|
169 |
),
|
170 |
'editor_options' => array (
|
171 |
+
'title' => __('Simple Text', 'fluentform'),
|
172 |
'icon_class' => 'icon-text-width',
|
173 |
'template' => 'inputText'
|
174 |
)
|
186 |
),
|
187 |
'settings' => array (
|
188 |
'container_class' => '',
|
189 |
+
'label' => __('Mask Input', 'fluentform'),
|
190 |
'label_placement' => '',
|
191 |
'admin_field_label' => '',
|
192 |
'help_message' => '',
|
194 |
'validation_rules' => array (
|
195 |
'required' => array (
|
196 |
'value' => false,
|
197 |
+
'message' => __('This field is required', 'fluentform'),
|
198 |
),
|
199 |
),
|
200 |
'conditional_logics' => array (),
|
201 |
),
|
202 |
'editor_options' => array (
|
203 |
+
'title' => __('Mask Input', 'fluentform'),
|
204 |
'icon_class' => 'icon-keyboard-o',
|
205 |
'template' => 'inputText'
|
206 |
)
|
219 |
),
|
220 |
'settings' => array (
|
221 |
'container_class' => '',
|
222 |
+
'label' => __('Textarea', 'fluentform'),
|
223 |
'admin_field_label' => '',
|
224 |
'label_placement' => '',
|
225 |
'help_message' => '',
|
226 |
'validation_rules' => array (
|
227 |
'required' => array (
|
228 |
'value' => false,
|
229 |
+
'message' => __('This field is required', 'fluentform'),
|
230 |
),
|
231 |
),
|
232 |
'conditional_logics' => array ()
|
233 |
),
|
234 |
'editor_options' => array (
|
235 |
+
'title' => __('Text Area', 'fluentform'),
|
236 |
'icon_class' => 'icon-paragraph',
|
237 |
'template' => 'inputTextarea'
|
238 |
),
|
247 |
'data-type' => 'address-element'
|
248 |
),
|
249 |
'settings' => array (
|
250 |
+
'label' => __('Address', 'fluentform'),
|
251 |
'admin_field_label' => '',
|
252 |
'conditional_logics' => array (),
|
253 |
),
|
264 |
),
|
265 |
'settings' => array (
|
266 |
'container_class' => '',
|
267 |
+
'label' => __('Address Line 1', 'fluentform'),
|
268 |
'admin_field_label' => '',
|
269 |
'help_message' => '',
|
270 |
'visible' => true,
|
271 |
'validation_rules' => array (
|
272 |
'required' => array (
|
273 |
'value' => false,
|
274 |
+
'message' => __('This field is required', 'fluentform'),
|
275 |
),
|
276 |
),
|
277 |
'conditional_logics' => array (),
|
292 |
),
|
293 |
'settings' => array (
|
294 |
'container_class' => '',
|
295 |
+
'label' => __('Address Line 2', 'fluentform'),
|
296 |
'admin_field_label' => '',
|
297 |
'help_message' => '',
|
298 |
'visible' => true,
|
299 |
'validation_rules' => array (
|
300 |
'required' => array (
|
301 |
'value' => false,
|
302 |
+
'message' => __('This field is required', 'fluentform'),
|
303 |
),
|
304 |
),
|
305 |
'conditional_logics' => array (),
|
320 |
),
|
321 |
'settings' => array (
|
322 |
'container_class' => '',
|
323 |
+
'label' => __('City', 'fluentform'),
|
324 |
'admin_field_label' => '',
|
325 |
'help_message' => '',
|
326 |
'error_message' => '',
|
328 |
'validation_rules' => array (
|
329 |
'required' => array (
|
330 |
'value' => false,
|
331 |
+
'message' => __('This field is required', 'fluentform'),
|
332 |
),
|
333 |
),
|
334 |
'conditional_logics' => array (),
|
349 |
),
|
350 |
'settings' => array (
|
351 |
'container_class' => '',
|
352 |
+
'label' => __('State', 'fluentform'),
|
353 |
'admin_field_label' => '',
|
354 |
'help_message' => '',
|
355 |
'error_message' => '',
|
357 |
'validation_rules' => array (
|
358 |
'required' => array (
|
359 |
'value' => false,
|
360 |
+
'message' => __('This field is required', 'fluentform'),
|
361 |
),
|
362 |
),
|
363 |
'conditional_logics' => array (),
|
379 |
),
|
380 |
'settings' => array (
|
381 |
'container_class' => '',
|
382 |
+
'label' => __('Zip Code', 'fluentform'),
|
383 |
'admin_field_label' => '',
|
384 |
'help_message' => '',
|
385 |
'error_message' => '',
|
387 |
'validation_rules' => array (
|
388 |
'required' => array (
|
389 |
'value' => false,
|
390 |
+
'message' => __('This field is required', 'fluentform'),
|
391 |
),
|
392 |
),
|
393 |
'conditional_logics' => array (),
|
408 |
),
|
409 |
'settings' => array (
|
410 |
'container_class' => '',
|
411 |
+
'label' => __('Country', 'fluentform'),
|
412 |
'admin_field_label' => '',
|
413 |
'help_message' => '',
|
414 |
'error_message' => '',
|
416 |
'validation_rules' => array (
|
417 |
'required' => array (
|
418 |
'value' => false,
|
419 |
+
'message' => __('This field is required', 'fluentform'),
|
420 |
),
|
421 |
),
|
422 |
'country_list' => array (
|
439 |
),
|
440 |
),
|
441 |
'editor_options' => array (
|
442 |
+
'title' => __('Address Fields', 'fluentform'),
|
443 |
'element' => 'address-fields',
|
444 |
'icon_class' => 'icon-credit-card',
|
445 |
'template' => 'addressFields'
|
457 |
),
|
458 |
'settings' => array (
|
459 |
'container_class' => '',
|
460 |
+
'label' => __('Country', 'fluentform'),
|
461 |
'admin_field_label' => '',
|
462 |
'label_placement' => '',
|
463 |
'help_message' => '',
|
464 |
'validation_rules' => array (
|
465 |
'required' => array (
|
466 |
'value' => false,
|
467 |
+
'message' => __('This field is required', 'fluentform'),
|
468 |
),
|
469 |
),
|
470 |
'country_list' => array (
|
479 |
'US' => 'United States of America',
|
480 |
),
|
481 |
'editor_options' => array (
|
482 |
+
'title' => __('Country List', 'fluentform'),
|
483 |
'element' => 'country-list',
|
484 |
'icon_class' => 'icon-globe',
|
485 |
'template' => 'selectCountry'
|
498 |
),
|
499 |
'settings' => array (
|
500 |
'container_class' => '',
|
501 |
+
'label' => __('Numeric Field', 'fluentform'),
|
502 |
'admin_field_label' => 'Numeric Field',
|
503 |
'label_placement' => '',
|
504 |
'help_message' => '',
|
505 |
'validation_rules' => array (
|
506 |
'required' => array (
|
507 |
'value' => false,
|
508 |
+
'message' => __('This field is required', 'fluentform'),
|
509 |
),
|
510 |
'numeric' => array (
|
511 |
'value' => true,
|
512 |
+
'message' => __('This field must contain numeric value', 'fluentform'),
|
513 |
),
|
514 |
'min' => array (
|
515 |
'value' => '',
|
516 |
+
'message' => __('Minimum value is ', 'fluentform'),
|
517 |
),
|
518 |
'max' => array (
|
519 |
'value' => '',
|
520 |
+
'message' => __('Maximum value is ', 'fluentform'),
|
521 |
),
|
522 |
),
|
523 |
'conditional_logics' => array (),
|
524 |
),
|
525 |
'editor_options' => array (
|
526 |
+
'title' => __('Numeric Field', 'fluentform'),
|
527 |
'icon_class' => 'icon-slack',
|
528 |
'template' => 'inputText'
|
529 |
),
|
538 |
'class' => '',
|
539 |
),
|
540 |
'settings' => array (
|
541 |
+
'label' => __('Dropdown', 'fluentform'),
|
542 |
'admin_field_label' => '',
|
543 |
'help_message' => '',
|
544 |
'container_class' => '',
|
547 |
'validation_rules' => array (
|
548 |
'required' => array (
|
549 |
'value' => false,
|
550 |
+
'message' => __('This field is required', 'fluentform'),
|
551 |
),
|
552 |
),
|
553 |
'conditional_logics' =>array (),
|
554 |
),
|
555 |
'options' => array (
|
556 |
+
'option 1' => 'Option 1',
|
557 |
+
'option 2' => 'Option 2',
|
558 |
),
|
559 |
'editor_options' => array (
|
560 |
+
'title' => __('Dropdown', 'fluentform'),
|
561 |
'icon_class' => 'icon-caret-square-o-down',
|
562 |
'element' => 'select',
|
563 |
'template' => 'select'
|
573 |
),
|
574 |
'settings' => array (
|
575 |
'container_class' => '',
|
576 |
+
'label' => __('Radio Field', 'fluentform'),
|
577 |
'admin_field_label' => '',
|
578 |
'label_placement' => '',
|
579 |
'display_type' => '',
|
581 |
'validation_rules' => array (
|
582 |
'required' => array (
|
583 |
'value' => false,
|
584 |
+
'message' => __('This field is required', 'fluentform'),
|
585 |
),
|
586 |
),
|
587 |
'conditional_logics' => array (),
|
591 |
'no' => 'No',
|
592 |
),
|
593 |
'editor_options' => array (
|
594 |
+
'title' => __('Radio Button', 'fluentform'),
|
595 |
'icon_class' => 'icon-dot-circle-o',
|
596 |
'element' => 'input-radio',
|
597 |
'template' => 'inputRadio'
|
607 |
),
|
608 |
'settings' => array (
|
609 |
'container_class' => '',
|
610 |
+
'label' => __('Checkbox Field', 'fluentform'),
|
611 |
'admin_field_label' => '',
|
612 |
'label_placement' => '',
|
613 |
'display_type' => '',
|
615 |
'validation_rules' => array (
|
616 |
'required' => array (
|
617 |
'value' => false,
|
618 |
+
'message' => __('This field is required', 'fluentform'),
|
619 |
),
|
620 |
),
|
621 |
'conditional_logics' => array (),
|
626 |
'item_3' => 'Item 3'
|
627 |
),
|
628 |
'editor_options' => array (
|
629 |
+
'title' => __('Check Box', 'fluentform'),
|
630 |
'icon_class' => 'icon-check-square-o',
|
631 |
'template' => 'inputCheckbox'
|
632 |
),
|
644 |
'settings' => array (
|
645 |
'help_message' => '',
|
646 |
'container_class' => '',
|
647 |
+
'label' => __('Multiselect', 'fluentform'),
|
648 |
'admin_field_label' => '',
|
649 |
'label_placement' => '',
|
650 |
'validation_rules' => array (
|
651 |
'required' => array (
|
652 |
'value' => false,
|
653 |
+
'message' => __('This field is required', 'fluentform'),
|
654 |
),
|
655 |
),
|
656 |
'conditional_logics' => array (),
|
657 |
),
|
658 |
'options' => array (
|
659 |
+
'Option 1' => 'Option 1',
|
660 |
+
'Option 2' => 'Option 2',
|
661 |
),
|
662 |
'editor_options' => array (
|
663 |
+
'title' => __('Multiple Choice', 'fluentform'),
|
664 |
'icon_class' => 'icon-list-ul',
|
665 |
'element' => 'select',
|
666 |
'template' => 'select'
|
678 |
),
|
679 |
'settings' => array (
|
680 |
'container_class' => '',
|
681 |
+
'label' => __('URL', 'fluentform'),
|
682 |
'admin_field_label' => '',
|
683 |
'label_placement' => '',
|
684 |
'help_message' => '',
|
685 |
'validation_rules' => array (
|
686 |
'required' => array (
|
687 |
'value' => false,
|
688 |
+
'message' => __('This field is required', 'fluentform'),
|
689 |
),
|
690 |
'url' => array (
|
691 |
'value' => true,
|
692 |
+
'message' => __('This field must contain a valid url', 'fluentform'),
|
693 |
),
|
694 |
),
|
695 |
'conditional_logics' => array (),
|
696 |
),
|
697 |
'editor_options' => array (
|
698 |
+
'title' => __('Website URL', 'fluentform'),
|
699 |
'icon_class' => 'icon-link',
|
700 |
'template' => 'inputText'
|
701 |
)
|
713 |
),
|
714 |
'settings' => array (
|
715 |
'container_class' => '',
|
716 |
+
'label' => __('Password', 'fluentform'),
|
717 |
'admin_field_label' => '',
|
718 |
'label_placement' => '',
|
719 |
'help_message' => '',
|
720 |
'validation_rules' => array (
|
721 |
'required' => array (
|
722 |
'value' => false,
|
723 |
+
'message' => __('This field is required', 'fluentform'),
|
724 |
),
|
725 |
),
|
726 |
'conditional_logics' => array (),
|
727 |
),
|
728 |
'editor_options' => array (
|
729 |
+
'title' => __('Password Field', 'fluentform'),
|
730 |
'icon_class' => 'icon-lock',
|
731 |
'template' => 'inputText'
|
732 |
),
|
744 |
),
|
745 |
'settings' => array (
|
746 |
'container_class' => '',
|
747 |
+
'label' => __('Date / Time', 'fluentform'),
|
748 |
'admin_field_label' => '',
|
749 |
'label_placement' => '',
|
750 |
'date_format' => 'd/m/Y',
|
753 |
'validation_rules' => array (
|
754 |
'required' => array (
|
755 |
'value' => false,
|
756 |
+
'message' => __('This field is required', 'fluentform'),
|
757 |
)
|
758 |
),
|
759 |
'conditional_logics' => array (),
|
760 |
),
|
761 |
'editor_options' => array (
|
762 |
+
'title' => __('Time & Date', 'fluentform'),
|
763 |
'icon_class' => 'icon-calendar-o',
|
764 |
'template' => 'inputText'
|
765 |
),
|
776 |
),
|
777 |
'settings' => array (
|
778 |
'container_class' => '',
|
779 |
+
'label' => __('File Upload', 'fluentform'),
|
780 |
'admin_field_label' => '',
|
781 |
'label_placement' => '',
|
782 |
'btn_text' => 'Choose File',
|
784 |
'validation_rules' => array (
|
785 |
'required' => array (
|
786 |
'value' => false,
|
787 |
+
'message' => __('This field is required', 'fluentform'),
|
788 |
),
|
789 |
'max_file_size' => array (
|
790 |
'value' => 1048576,
|
791 |
'_valueFrom' => 'MB',
|
792 |
+
'message' => __('Maximum file size limit', 'fluentform')
|
793 |
),
|
794 |
'max_file_count' => array (
|
795 |
'value' => 1,
|
796 |
+
'message' => __('Max file count', 'fluentform')
|
797 |
),
|
798 |
'allowed_file_types' => array (
|
799 |
'value' => array(),
|
800 |
+
'message' => __('allowed_file_types', 'fluentform')
|
801 |
)
|
802 |
),
|
803 |
'conditional_logics' => array (),
|
804 |
),
|
805 |
'editor_options' => array (
|
806 |
+
'title' => __('File Upload', 'fluentform'),
|
807 |
'icon_class' => 'icon-upload',
|
808 |
'template' => 'inputFile'
|
809 |
),
|
821 |
),
|
822 |
'settings' => array (
|
823 |
'container_class' => '',
|
824 |
+
'label' => __('Image Upload', 'fluentform'),
|
825 |
'admin_field_label' => '',
|
826 |
'label_placement' => '',
|
827 |
'btn_text' => 'Choose File',
|
829 |
'validation_rules' => array (
|
830 |
'required' => array (
|
831 |
'value' => false,
|
832 |
+
'message' => __('This field is required', 'fluentform'),
|
833 |
),
|
834 |
'max_file_size' => array (
|
835 |
'value' => 1048576,
|
836 |
'_valueFrom' => 'MB',
|
837 |
+
'message' => __('Maximum file size limit', 'fluentform')
|
838 |
),
|
839 |
'max_file_count' => array (
|
840 |
'value' => 1,
|
841 |
+
'message' => __('Max file count', 'fluentform')
|
842 |
),
|
843 |
'allowed_image_types' => array (
|
844 |
'value' => array(),
|
845 |
+
'message' => __('Allowed image size does not match', 'fluentform')
|
846 |
)
|
847 |
),
|
848 |
'conditional_logics' => array (),
|
849 |
),
|
850 |
'editor_options' => array (
|
851 |
+
'title' => __('Image Upload', 'fluentform'),
|
852 |
'icon_class' => 'icon-picture-o',
|
853 |
'template' => 'inputFile'
|
854 |
),
|
861 |
'data-type' => 'repeat-element'
|
862 |
),
|
863 |
'settings' => array (
|
864 |
+
'label' => __('Repeat Fields', 'fluentform'),
|
865 |
'admin_field_label' => '',
|
866 |
'container_class' => '',
|
867 |
'label_placement' => '',
|
878 |
'placeholder' => '',
|
879 |
),
|
880 |
'settings' => array (
|
881 |
+
'label' => __('Column 1', 'fluentform'),
|
882 |
'help_message' => '',
|
883 |
'validation_rules' => array (
|
884 |
'required' => array (
|
885 |
'value' => false,
|
886 |
+
'message' => __('This field is required', 'fluentform'),
|
887 |
)
|
888 |
)
|
889 |
),
|
891 |
)
|
892 |
),
|
893 |
'editor_options' => array (
|
894 |
+
'title' => __('Repeat Field', 'fluentform'),
|
895 |
'icon_class' => 'icon-text-width',
|
896 |
'template' => 'repeatFields'
|
897 |
),
|
938 |
'admin_field_label' => ''
|
939 |
),
|
940 |
'editor_options' => array (
|
941 |
+
'title' => __('Hidden Field', 'fluentform'),
|
942 |
'icon_class' => 'icon-eye-slash',
|
943 |
'template' => 'inputHidden',
|
944 |
),
|
951 |
'class' => '',
|
952 |
),
|
953 |
'settings' => array(
|
954 |
+
'label' => __('Section Break', 'fluentform'),
|
955 |
+
'description' => __('Some description about this section', 'fluentform'),
|
956 |
'align' => 'left',
|
957 |
'conditional_logics' => array(),
|
958 |
),
|
959 |
'editor_options' => array(
|
960 |
+
'title' => __('Section Break', 'fluentform'),
|
961 |
'icon_class' => 'icon-puzzle-piece',
|
962 |
'template' => 'sectionBreak',
|
963 |
),
|
972 |
'validation_rules' => array(),
|
973 |
),
|
974 |
'editor_options' => array(
|
975 |
+
'title' => __('reCaptcha', 'fluentform'),
|
976 |
'icon_class' => 'icon-qrcode',
|
977 |
'why_disabled_modal' => 'recaptcha',
|
978 |
'template' => 'recaptcha',
|
987 |
'conditional_logics' => array()
|
988 |
),
|
989 |
'editor_options' => array(
|
990 |
+
'title' => __('Custom HTML', 'fluentform'),
|
991 |
'icon_class' => 'icon-code',
|
992 |
'template' => 'customHTML',
|
993 |
)
|
1004 |
'conditional_logics' => array(),
|
1005 |
),
|
1006 |
'editor_options' => array(
|
1007 |
+
'title' => __('Shortcode', 'fluentform'),
|
1008 |
'icon_class' => 'icon-certificate',
|
1009 |
'template' => 'shortcode',
|
1010 |
)
|
1021 |
'settings' => array(
|
1022 |
'tnc_html' => 'I have read and agree to the <a href="#">Terms and Conditions</a> and <a href="#">Privacy Policy</a>',
|
1023 |
'has_checkbox' => true,
|
1024 |
+
'admin_field_label' => __('Terms and Conditions', 'fluentform'),
|
1025 |
'container_class' => '',
|
1026 |
'validation_rules' => array (
|
1027 |
'required' => array (
|
1028 |
'value' => false,
|
1029 |
+
'message' => __('This field is required', 'fluentform'),
|
1030 |
),
|
1031 |
),
|
1032 |
'conditional_logics' => array(),
|
1033 |
),
|
1034 |
'editor_options' => array(
|
1035 |
+
'title' => __('Terms & Conditions', 'fluentform'),
|
1036 |
'icon_class' => 'icon-check-square-o',
|
1037 |
'template' => 'termsCheckbox'
|
1038 |
),
|
1049 |
'conditional_logics' => array(),
|
1050 |
),
|
1051 |
'editor_options' => array(
|
1052 |
+
'title' => __('Action Hook', 'fluentform'),
|
1053 |
'icon_class' => 'icon-paragraph',
|
1054 |
'template' => 'actionHook'
|
1055 |
)
|
1064 |
'settings' => [
|
1065 |
'prev_btn' => [
|
1066 |
'type' => 'default',
|
1067 |
+
'text' => __('Previous', 'fluentform'),
|
1068 |
'img_url' => '',
|
1069 |
],
|
1070 |
'next_btn' => [
|
1071 |
'type' => 'default',
|
1072 |
+
'text' => __('Next', 'fluentform'),
|
1073 |
'img_url' => '',
|
1074 |
],
|
1075 |
],
|
1076 |
'editor_options' => [
|
1077 |
+
'title' => __('Form Step', 'fluentform'),
|
1078 |
'icon_class' => 'icon-step-forward',
|
1079 |
'template' => 'formStep',
|
1080 |
],
|
1088 |
'name' => 'ratings',
|
1089 |
),
|
1090 |
'settings' => array (
|
1091 |
+
'label' => __('Ratings','fluentform'),
|
1092 |
'show_text' => false,
|
1093 |
'help_message' => '',
|
1094 |
'label_placement' => '',
|
1098 |
'validation_rules' => array (
|
1099 |
'required' => array (
|
1100 |
'value' => false,
|
1101 |
+
'message' => __('This field is required', 'fluentform'),
|
1102 |
),
|
1103 |
),
|
1104 |
),
|
1105 |
'options' => array (
|
1106 |
+
'1' => __('Nice', 'fluentform'),
|
1107 |
+
'2' => __('Good', 'fluentform'),
|
1108 |
+
'3' => __('Very Good', 'fluentform'),
|
1109 |
+
'4' => __('Awesome', 'fluentform'),
|
1110 |
+
'5' => __('Amazing', 'fluentform'),
|
1111 |
),
|
1112 |
'editor_options' => array (
|
1113 |
+
'title' => __('Ratings', 'fluentform'),
|
1114 |
'icon_class' => 'icon-eye-slash',
|
1115 |
'template' => 'ratings',
|
1116 |
),
|
1125 |
'settings' => array (
|
1126 |
'tabular_field_type' => 'checkbox',
|
1127 |
'container_class' => '',
|
1128 |
+
'label' => __('Checkbox Grid', 'fluentform'),
|
1129 |
'admin_field_label' => '',
|
1130 |
'label_placement' => '',
|
1131 |
'help_message' => '',
|
1132 |
'validation_rules' => array (
|
1133 |
'required' => array (
|
1134 |
'value' => false,
|
1135 |
+
'message' => __('This field is required', 'fluentform'),
|
1136 |
'per_row' => false,
|
1137 |
),
|
1138 |
),
|
1146 |
'selected_grids' => array ()
|
1147 |
),
|
1148 |
'editor_options' => array (
|
1149 |
+
'title' => __('Checkable Grid', 'fluentform'),
|
1150 |
'icon_class' => 'icon-dot-circle-o',
|
1151 |
'template' => 'checkableGrids'
|
1152 |
),
|
1161 |
'class' => '',
|
1162 |
),
|
1163 |
'settings' => array(
|
1164 |
+
'label' => __('GDPR Agreement', 'fluentform'),
|
1165 |
+
'tnc_html' => __('I consent to having this website store my submitted information so they can respond to my inquiry', 'fluentform'),
|
1166 |
+
'admin_field_label' => __('GDPR Agreement', 'fluentform'),
|
1167 |
'has_checkbox' => true,
|
1168 |
'container_class' => '',
|
1169 |
'validation_rules' => array (
|
1170 |
'required' => array (
|
1171 |
'value' => true,
|
1172 |
+
'message' => __('This field is required', 'fluentform'),
|
1173 |
),
|
1174 |
),
|
1175 |
'conditional_logics' => array(),
|
1176 |
),
|
1177 |
'editor_options' => array(
|
1178 |
+
'title' => __('GDPR Agreement', 'fluentform'),
|
1179 |
'icon_class' => 'icon-check-square-o',
|
1180 |
'template' => 'termsCheckbox'
|
1181 |
),
|
1193 |
),
|
1194 |
'editor_options' =>
|
1195 |
array(
|
1196 |
+
'title' => __('Two Column Container', 'fluentform'),
|
1197 |
'icon_class' => 'icon-columns',
|
1198 |
),
|
1199 |
),
|
1208 |
array( 'fields' => array() ),
|
1209 |
),
|
1210 |
'editor_options' => array(
|
1211 |
+
'title' => __('Three Column Container', 'fluentform'),
|
1212 |
'icon_class' => 'icon-columns',
|
1213 |
),
|
1214 |
)
|
1219 |
'index' => 17,
|
1220 |
'element' => 'product',
|
1221 |
'settings' => array(
|
1222 |
+
'label' => __('Product Name', 'fluentform'),
|
1223 |
'description' => '',
|
1224 |
'product_field_types' => '',
|
1225 |
'product_type' => 'single',
|
1231 |
'validation_rules' => array (
|
1232 |
'required' => array (
|
1233 |
'value' => false,
|
1234 |
+
'message' => __('This field is required', 'fluentform'),
|
1235 |
),
|
1236 |
),
|
1237 |
'conditional_logics' => array (),
|
1239 |
'field_types' => array(
|
1240 |
array(
|
1241 |
'value' => 'single',
|
1242 |
+
'label' => __('Single Product', 'fluentform')
|
1243 |
),
|
1244 |
array(
|
1245 |
'value' => 'dropdown',
|
1246 |
+
'label' => __('Drop Down', 'fluentform')
|
1247 |
),
|
1248 |
array(
|
1249 |
'value' => 'radio',
|
1250 |
+
'label' => __('Radio Buttons', 'fluentform')
|
1251 |
)
|
1252 |
),
|
1253 |
'grid_columns' => array (
|
1267 |
),
|
1268 |
),
|
1269 |
),
|
1270 |
+
);
|
app/Services/FormBuilder/EditorShortCode.php
CHANGED
@@ -6,11 +6,14 @@ use FluentForm\App\Modules\Form\FormFieldsParser;
|
|
6 |
|
7 |
class EditorShortcode
|
8 |
{
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
'title' => 'General Shortcodes',
|
13 |
'shortcodes' => [
|
|
|
|
|
|
|
14 |
'{ip}' => __('IP Address', 'fluentform'),
|
15 |
'{date.m/d/Y}' => __('Date (mm/dd/yyyy)', 'fluentform'),
|
16 |
'{date.d/m/Y}' => __('Date (dd/mm/yyyy)', 'fluentform'),
|
@@ -27,78 +30,78 @@ class EditorShortcode
|
|
27 |
'{browser.platform}' => __('User Operating System', 'fluentform')
|
28 |
]
|
29 |
];
|
30 |
-
|
31 |
|
32 |
-
|
33 |
-
|
34 |
$formFields = FormFieldsParser::getShortCodeInputs(
|
35 |
-
|
36 |
'admin_label', 'attributes', 'options'
|
37 |
]);
|
38 |
|
39 |
$formShortCodes = [
|
40 |
-
|
41 |
-
|
42 |
];
|
43 |
|
44 |
$formShortCodes['shortcodes']['{all_data}'] = 'All Submitted Data';
|
45 |
foreach ($formFields as $key => $value) {
|
46 |
-
|
47 |
}
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
}
|
6 |
|
7 |
class EditorShortcode
|
8 |
{
|
9 |
+
public static function getGeneralShortCodes()
|
10 |
+
{
|
11 |
+
return [
|
12 |
'title' => 'General Shortcodes',
|
13 |
'shortcodes' => [
|
14 |
+
'{wp.admin_email}' => __('Admin Email', 'fluentform'),
|
15 |
+
'{wp.site_url}' => __('Site URL', 'fluentform'),
|
16 |
+
'{wp.site_title}' => __('Site Title', 'fluentform'),
|
17 |
'{ip}' => __('IP Address', 'fluentform'),
|
18 |
'{date.m/d/Y}' => __('Date (mm/dd/yyyy)', 'fluentform'),
|
19 |
'{date.d/m/Y}' => __('Date (dd/mm/yyyy)', 'fluentform'),
|
30 |
'{browser.platform}' => __('User Operating System', 'fluentform')
|
31 |
]
|
32 |
];
|
33 |
+
}
|
34 |
|
35 |
+
public static function getFormShortCodes($form)
|
36 |
+
{
|
37 |
$formFields = FormFieldsParser::getShortCodeInputs(
|
38 |
+
static::getForm($form), [
|
39 |
'admin_label', 'attributes', 'options'
|
40 |
]);
|
41 |
|
42 |
$formShortCodes = [
|
43 |
+
'shortcodes' => [],
|
44 |
+
'title' => 'Input Options'
|
45 |
];
|
46 |
|
47 |
$formShortCodes['shortcodes']['{all_data}'] = 'All Submitted Data';
|
48 |
foreach ($formFields as $key => $value) {
|
49 |
+
$formShortCodes['shortcodes']['{inputs.' . $key . '}'] = $value['admin_label'];
|
50 |
}
|
51 |
|
52 |
+
return $formShortCodes;
|
53 |
+
}
|
54 |
+
|
55 |
+
public static function getShortCodes($form)
|
56 |
+
{
|
57 |
+
return [
|
58 |
+
static::getFormShortCodes($form),
|
59 |
+
static::getGeneralShortCodes()
|
60 |
+
];
|
61 |
+
}
|
62 |
+
|
63 |
+
public static function parse($string, $data, callable $arrayFormatter = null)
|
64 |
+
{
|
65 |
+
if (is_array($string)) {
|
66 |
+
return static::parseArray($string, $data, $arrayFormatter);
|
67 |
+
}
|
68 |
+
|
69 |
+
return static::parseString($string, $data, $arrayFormatter);
|
70 |
+
}
|
71 |
+
|
72 |
+
public static function parseArray($string, $data, $arrayFormatter)
|
73 |
+
{
|
74 |
+
foreach ($string as $key => $value) {
|
75 |
+
if (is_array($value)) {
|
76 |
+
$string[$key] = static::parseArray($value, $data, $arrayFormatter);
|
77 |
+
} else {
|
78 |
+
$string[$key] = static::parseString($value, $data, $arrayFormatter);
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
return $string;
|
83 |
+
}
|
84 |
+
|
85 |
+
public static function parseString($string, $data, callable $arrayFormatter = null)
|
86 |
+
{
|
87 |
+
return preg_replace_callback('/{+(.*?)}/', function ($matches) use (&$data, &$arrayFormatter) {
|
88 |
+
if (!isset($data[$matches[1]])) {
|
89 |
+
return $matches[0];
|
90 |
+
} elseif (is_array($value = $data[$matches[1]])) {
|
91 |
+
return is_callable($arrayFormatter) ? $arrayFormatter($value) : implode(', ', $value);
|
92 |
+
}
|
93 |
+
|
94 |
+
return $data[$matches[1]];
|
95 |
+
|
96 |
+
}, $string);
|
97 |
+
}
|
98 |
+
|
99 |
+
protected static function getForm($form)
|
100 |
+
{
|
101 |
+
if (is_object($form)) {
|
102 |
+
return $form;
|
103 |
+
}
|
104 |
+
|
105 |
+
return wpFluent()->table('fluentform_forms')->find($form);
|
106 |
+
}
|
107 |
}
|
app/Services/FormBuilder/EditorShortcodeParser.php
CHANGED
@@ -26,6 +26,10 @@ class EditorShortcodeParser
|
|
26 |
'embed_post.post_title' => 'parsePostProperties',
|
27 |
'embed_post.permalink' => 'parsePostProperties',
|
28 |
|
|
|
|
|
|
|
|
|
29 |
'user.ID' => 'parseUserProperties',
|
30 |
'user.display_name' => 'parseUserProperties',
|
31 |
'user.first_name' => 'parseUserProperties',
|
@@ -55,12 +59,13 @@ class EditorShortcodeParser
|
|
55 |
if (isset(static::$handlers[$handler])) {
|
56 |
$filteredValue .= call_user_func_array(
|
57 |
[__CLASS__, static::$handlers[$handler]],
|
58 |
-
['{'
|
59 |
);
|
60 |
} elseif (strpos($handler, 'get.') !== false) {
|
61 |
$filteredValue .= static::parseQueryParam($handler);
|
62 |
} else {
|
63 |
-
|
|
|
64 |
}
|
65 |
}
|
66 |
|
@@ -124,6 +129,26 @@ class EditorShortcodeParser
|
|
124 |
return $value;
|
125 |
}
|
126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
/**
|
128 |
* Parse browser/user-agent properties
|
129 |
* @param string $value
|
@@ -167,13 +192,13 @@ class EditorShortcodeParser
|
|
167 |
/**
|
168 |
* Parse request query param.
|
169 |
*
|
170 |
-
* @param string
|
171 |
* @param \stdClass $form
|
172 |
* @return string
|
173 |
*/
|
174 |
public static function parseQueryParam($value)
|
175 |
{
|
176 |
-
|
177 |
$param = array_pop($exploded);
|
178 |
|
179 |
return isset($_REQUEST[$param]) ? wp_kses_post($_REQUEST[$param]) : null;
|
26 |
'embed_post.post_title' => 'parsePostProperties',
|
27 |
'embed_post.permalink' => 'parsePostProperties',
|
28 |
|
29 |
+
'wp.admin_email' => 'parseWPProperties',
|
30 |
+
'wp.site_url' => 'parseWPProperties',
|
31 |
+
'wp.site_title' => 'parseWPProperties',
|
32 |
+
|
33 |
'user.ID' => 'parseUserProperties',
|
34 |
'user.display_name' => 'parseUserProperties',
|
35 |
'user.first_name' => 'parseUserProperties',
|
59 |
if (isset(static::$handlers[$handler])) {
|
60 |
$filteredValue .= call_user_func_array(
|
61 |
[__CLASS__, static::$handlers[$handler]],
|
62 |
+
['{' . $handler . '}', $form]
|
63 |
);
|
64 |
} elseif (strpos($handler, 'get.') !== false) {
|
65 |
$filteredValue .= static::parseQueryParam($handler);
|
66 |
} else {
|
67 |
+
// In not found then return the original please
|
68 |
+
$filteredValue .= '{' . $handler . '}';
|
69 |
}
|
70 |
}
|
71 |
|
129 |
return $value;
|
130 |
}
|
131 |
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Parse WP Properties
|
135 |
+
* @param string $value
|
136 |
+
* @return string
|
137 |
+
*/
|
138 |
+
private static function parseWPProperties($value, $form = null)
|
139 |
+
{
|
140 |
+
if($value == '{wp.admin_email}') {
|
141 |
+
return get_option('admin_email');
|
142 |
+
}
|
143 |
+
if($value == '{wp.site_url}') {
|
144 |
+
return site_url();
|
145 |
+
}
|
146 |
+
if($value == '{wp.site_title}') {
|
147 |
+
return get_option('blogname');
|
148 |
+
}
|
149 |
+
return $value;
|
150 |
+
}
|
151 |
+
|
152 |
/**
|
153 |
* Parse browser/user-agent properties
|
154 |
* @param string $value
|
192 |
/**
|
193 |
* Parse request query param.
|
194 |
*
|
195 |
+
* @param string $value
|
196 |
* @param \stdClass $form
|
197 |
* @return string
|
198 |
*/
|
199 |
public static function parseQueryParam($value)
|
200 |
{
|
201 |
+
$exploded = explode('.', $value);
|
202 |
$param = array_pop($exploded);
|
203 |
|
204 |
return isset($_REQUEST[$param]) ? wp_kses_post($_REQUEST[$param]) : null;
|
app/Services/FormBuilder/ElementCustomization.php
CHANGED
@@ -15,154 +15,118 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
15 |
exit;
|
16 |
}
|
17 |
|
|
|
|
|
18 |
$element_customization_settings = array(
|
19 |
'name' => array(
|
20 |
'template' => 'nameAttr',
|
21 |
-
'label' => 'Name Attribute',
|
22 |
-
'help_text' => 'This is the field name attributes which is used to submit form data, name attribute must be unique.',
|
23 |
),
|
24 |
'label' => array(
|
25 |
'template' => 'inputText',
|
26 |
-
'label' => 'Element Label',
|
27 |
-
'help_text' => 'This is the field title the user will see when filling out the form.',
|
28 |
),
|
29 |
'label_placement' => array(
|
30 |
'template' => 'radioButton',
|
31 |
-
'label' => 'Label Placement',
|
32 |
-
'help_text' => 'Determine the position of label title where the user will see this. By choosing "Default", global label placement setting will be applied.',
|
33 |
'options' => array(
|
34 |
array(
|
35 |
'value' => '',
|
36 |
-
'label' => 'Default',
|
37 |
),
|
38 |
array(
|
39 |
'value' => 'top',
|
40 |
-
'label' => 'Top',
|
41 |
),
|
42 |
array(
|
43 |
'value' => 'left',
|
44 |
-
'label' => 'Left',
|
45 |
),
|
46 |
array(
|
47 |
'value' => 'right',
|
48 |
-
'label' => 'Right',
|
49 |
),
|
50 |
),
|
51 |
),
|
52 |
'button_style' => array (
|
53 |
'template' => 'selectBtnStyle',
|
54 |
-
'label' => 'Button Style',
|
55 |
-
'help_text' => 'Select a button style from the dropdown',
|
56 |
),
|
57 |
'button_size' => array (
|
58 |
'template' => 'radioButton',
|
59 |
-
'label' => 'Button Size',
|
60 |
-
'help_text' => 'Define a size of the button',
|
61 |
'options' => array(
|
62 |
array(
|
63 |
'value' => 'sm',
|
64 |
-
'label' => 'Small',
|
65 |
),
|
66 |
array(
|
67 |
'value' => 'md',
|
68 |
-
'label' => 'Medium',
|
69 |
),
|
70 |
array(
|
71 |
'value' => 'lg',
|
72 |
-
'label' => 'Large',
|
73 |
),
|
74 |
)
|
75 |
),
|
76 |
'placeholder' => array(
|
77 |
'template' => 'inputText',
|
78 |
-
'label' => 'Placeholder',
|
79 |
-
'help_text' => 'This is the field placeholder, the user will see this if the input field is empty.',
|
80 |
),
|
81 |
'date_format' => array(
|
82 |
'template' => 'select',
|
83 |
-
'label' => 'Date Format',
|
84 |
-
'
|
85 |
-
'
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
),
|
90 |
-
array(
|
91 |
-
'value' => 'd-m-Y',
|
92 |
-
'label' => 'DD-MM-YYYY',
|
93 |
-
),
|
94 |
-
array(
|
95 |
-
'value' => 'm/d/Y',
|
96 |
-
'label' => 'MM/DD/YYYY',
|
97 |
-
),
|
98 |
-
array(
|
99 |
-
'value' => 'm-d-Y',
|
100 |
-
'label' => 'MM-DD-YYYY',
|
101 |
-
),
|
102 |
-
array(
|
103 |
-
'value' => 'F d, Y',
|
104 |
-
'label' => 'December 20, 2017',
|
105 |
-
),
|
106 |
-
array(
|
107 |
-
'value' => 'd/m/Y h:i:s A',
|
108 |
-
'label' => 'DD/MM/YYYY HH:MM:SS AM',
|
109 |
-
),
|
110 |
-
array(
|
111 |
-
'value' => 'd-m-Y h:i:s A',
|
112 |
-
'label' => 'DD-MM-YYYY HH:MM:SS AM',
|
113 |
-
),
|
114 |
-
array(
|
115 |
-
'value' => 'm/d/Y h:i:s A',
|
116 |
-
'label' => 'MM/DD/YYYY HH:MM:SS AM',
|
117 |
-
),
|
118 |
-
array(
|
119 |
-
'value' => 'm-d-Y h:i:s A',
|
120 |
-
'label' => 'MM-DD-YYYY HH:MM:SS AM',
|
121 |
-
),
|
122 |
-
array(
|
123 |
-
'value' => 'F d, Y h:i:s A',
|
124 |
-
'label' => 'December 20, 2017 09:59:23 AM',
|
125 |
-
),
|
126 |
-
),
|
127 |
),
|
128 |
'rows' => array(
|
129 |
'template' => 'inputText',
|
130 |
-
'label' => 'Rows',
|
131 |
-
'help_text' => 'How many rows will textarea take in a form. It\'s an HTML attributes for browser support.',
|
132 |
),
|
133 |
'cols' => array(
|
134 |
'template' => 'inputText',
|
135 |
-
'label' => 'Columns',
|
136 |
-
'help_text' => 'How many cols will textarea take in a form. It\'s an HTML attributes for browser support.',
|
137 |
),
|
138 |
'options' => array(
|
139 |
'template' => 'selectOptions',
|
140 |
-
'label' => 'Options',
|
141 |
-
'help_text' => 'Create options for the field and checkmark them for default selection.',
|
142 |
),
|
143 |
'validation_rules' => array(
|
144 |
'template' => 'validationRulesForm',
|
145 |
-
'label' => 'Validation Rules',
|
146 |
'help_text' => '',
|
147 |
),
|
148 |
'tnc_html' => array(
|
149 |
'template' => 'inputTextarea',
|
150 |
-
'label' => 'Terms & Conditions',
|
151 |
-
'help_text' => 'Write HTML content for terms & condition checkbox',
|
152 |
'rows' => 5,
|
153 |
'cols' => 3,
|
154 |
),
|
155 |
'hook_name' => array(
|
156 |
'template' => 'customHookName',
|
157 |
-
'label' => 'Hook Name',
|
158 |
-
'help_text' => 'WordPress Hook name to hook something in this place.',
|
159 |
),
|
160 |
'has_checkbox' => array(
|
161 |
'template' => 'inputCheckbox',
|
162 |
'options' => array(
|
163 |
array(
|
164 |
'value' => true,
|
165 |
-
'label' => 'Show Checkbox',
|
166 |
),
|
167 |
),
|
168 |
),
|
@@ -170,198 +134,193 @@ $element_customization_settings = array(
|
|
170 |
'template' => 'inputTextarea',
|
171 |
'rows' => 4,
|
172 |
'cols' => 2,
|
173 |
-
'label' => 'HTML Code',
|
174 |
-
'help_text' => 'Your valid HTML code will be shown to the user as normal content.',
|
175 |
),
|
176 |
'description' => array(
|
177 |
'template' => 'inputTextarea',
|
178 |
'rows' => 4,
|
179 |
'cols' => 2,
|
180 |
-
'label' => 'Description',
|
181 |
-
'help_text' => 'Description will be shown to the user as normal text content.',
|
182 |
),
|
183 |
'btn_text' => array(
|
184 |
'template' => 'inputText',
|
185 |
-
'label' => 'Button Text',
|
186 |
-
'help_text' => 'This will be visible as button text for upload file.',
|
187 |
),
|
188 |
'button_ui' => array(
|
189 |
'template' => 'prevNextButton',
|
190 |
-
'label' => 'Submit Button',
|
191 |
-
'help_text' => 'This is form submission button.',
|
192 |
),
|
193 |
'align' => array(
|
194 |
'template' => 'radio',
|
195 |
-
'label' => 'Content Alignment',
|
196 |
-
'help_text' => 'How the content will be aligned.',
|
197 |
'options' => array(
|
198 |
array(
|
199 |
'value' => 'left',
|
200 |
-
'label' => 'Left',
|
201 |
),
|
202 |
array(
|
203 |
'value' => 'center',
|
204 |
-
'label' => 'Center',
|
205 |
),
|
206 |
array(
|
207 |
'value' => 'right',
|
208 |
-
'label' => 'Right',
|
209 |
),
|
210 |
),
|
211 |
),
|
212 |
'shortcode' => array(
|
213 |
'template' => 'inputText',
|
214 |
-
'label' => 'Shortcode',
|
215 |
-
'help_text' => 'Your shortcode to render desired content in current place.',
|
216 |
),
|
217 |
'apply_styles' => array (
|
218 |
'template' => 'radioButton',
|
219 |
-
'label' => 'Apply Styles',
|
220 |
-
'help_text' => 'Apply styles provided here',
|
221 |
'options' => array(
|
222 |
array(
|
223 |
'value' => true,
|
224 |
-
'label' => 'Yes',
|
225 |
),
|
226 |
array(
|
227 |
'value' => false,
|
228 |
-
'label' => 'No',
|
229 |
)
|
230 |
),
|
231 |
),
|
232 |
'step_title' => array(
|
233 |
'template' => 'inputText',
|
234 |
-
'label' => 'Step Title',
|
235 |
-
'help_text' => 'Form step titles, user will see each title in each step.',
|
236 |
),
|
237 |
'progress_indicator' => array(
|
238 |
'template' => 'radio',
|
239 |
-
'label' => 'Progress Indicator',
|
240 |
-
'help_text' => 'Select any of them below, user will see progress of form steps according to your choice.',
|
241 |
'options' => array(
|
242 |
array(
|
243 |
'value' => 'progress-bar',
|
244 |
-
'label' => 'Progress Bar',
|
245 |
),
|
246 |
array(
|
247 |
'value' => 'steps',
|
248 |
-
'label' => 'Steps',
|
249 |
),
|
250 |
array(
|
251 |
'value' => '',
|
252 |
-
'label' => 'None',
|
253 |
),
|
254 |
),
|
255 |
),
|
256 |
'step_titles' => array(
|
257 |
'template' => 'customStepTitles',
|
258 |
-
'label' => 'Step Titles',
|
259 |
-
'help_text' => 'Form step titles, user will see each title in each step.',
|
260 |
),
|
261 |
'prev_btn' => array(
|
262 |
'template' => 'prevNextButton',
|
263 |
-
'label' => 'Previous Button',
|
264 |
-
'help_text' => 'Multi-step form\'s previous button',
|
265 |
),
|
266 |
'next_btn' => array(
|
267 |
'template' => 'prevNextButton',
|
268 |
-
'label' => 'Next Button',
|
269 |
-
'help_text' => 'Multi-step form\'s next button',
|
270 |
),
|
271 |
'address_fields' => array(
|
272 |
'template' => 'addressFields',
|
273 |
-
'label' => 'Address Fields',
|
274 |
),
|
275 |
'name_fields' => array(
|
276 |
'template' => 'nameFields',
|
277 |
-
'label' => 'Name Fields',
|
278 |
),
|
279 |
'multi_column' => array(
|
280 |
'template' => 'inputCheckbox',
|
281 |
'options' => array(
|
282 |
array(
|
283 |
'value' => true,
|
284 |
-
'label' => 'Enable Multiple Columns',
|
285 |
),
|
286 |
),
|
287 |
),
|
288 |
'repeat_fields' => array(
|
289 |
'template' => 'customRepeatFields',
|
290 |
-
'label' => 'Repeat Fields',
|
291 |
-
'help_text' => 'This is a form field which a user will be able to repeat.',
|
292 |
),
|
293 |
'admin_field_label' => array(
|
294 |
'template' => 'inputText',
|
295 |
-
'label' => 'Admin Field Label',
|
296 |
-
'help_text' => 'Admin field label is field title which will be used for admin field title.',
|
297 |
),
|
298 |
'value' => array(
|
299 |
'template' => 'inputValue',
|
300 |
-
'label' => 'Default Value',
|
301 |
-
'help_text' => 'If you would like to pre-populate the value of a field, enter it here.',
|
302 |
),
|
303 |
'container_class' => array(
|
304 |
'template' => 'inputText',
|
305 |
-
'label' => 'Container Class',
|
306 |
-
'help_text' => 'Class for the field wrapper. This can be used to style current element.',
|
307 |
),
|
308 |
'class' => array(
|
309 |
'template' => 'inputText',
|
310 |
-
'label' => 'Element Class',
|
311 |
-
'help_text' => 'Class for the field. This can be used to style current element.',
|
312 |
),
|
313 |
'country_list' => array(
|
314 |
'template' => 'customCountryList',
|
315 |
-
'label' => 'Country List',
|
316 |
),
|
317 |
'product_field_types' => array(
|
318 |
'template' => 'productFieldTypes',
|
319 |
-
'label' => 'Options',
|
320 |
),
|
321 |
'help_message' => array(
|
322 |
'template' => 'inputText',
|
323 |
-
'label' => 'Help Message',
|
324 |
-
'help_text' => 'Help message will be shown as tooltip next to sidebar or below the field.',
|
325 |
),
|
326 |
'conditional_logics' => array(
|
327 |
'template' => 'conditionalLogics',
|
328 |
-
'label' => 'Conditional Logic',
|
329 |
-
'help_text' => 'Create rules to dynamically display or hide this field based on values from another field.',
|
330 |
-
),
|
331 |
-
// 'data-name' => array(
|
332 |
-
// 'template' => 'dataName',
|
333 |
-
// 'label' => 'Data Name',
|
334 |
-
// 'help_text' => 'This is a custom item for learning...',
|
335 |
-
// ),
|
336 |
'background_color' => array(
|
337 |
'template' => 'inputColor',
|
338 |
-
'label' => 'Background Color',
|
339 |
-
'help_text' => 'The Background color of the element'
|
340 |
),
|
341 |
'color' => array(
|
342 |
'template' => 'inputColor',
|
343 |
-
'label' => 'Font Color',
|
344 |
-
'help_text' => 'Font color of the element'
|
345 |
),
|
346 |
'show_text' => array(
|
347 |
'template' => 'radio',
|
348 |
-
'label' => 'Show Text?',
|
349 |
-
'help_text' => 'Show Text beside the ratings',
|
350 |
'options' => array(
|
351 |
array(
|
352 |
'value' => true,
|
353 |
-
'label' => 'Yes',
|
354 |
),
|
355 |
array(
|
356 |
'value' => false,
|
357 |
-
'label' => 'No',
|
358 |
)
|
359 |
),
|
360 |
),
|
361 |
'data-mask' => array (
|
362 |
'template' => 'customMask',
|
363 |
-
'label' => 'Custom Mask',
|
364 |
-
'help_text' => 'Write your own mask for this input',
|
365 |
'dependency' => array (
|
366 |
'depends_on' => 'settings/temp_mask',
|
367 |
'value' => 'custom',
|
@@ -370,12 +329,12 @@ $element_customization_settings = array(
|
|
370 |
),
|
371 |
'temp_mask' => array (
|
372 |
'template' => 'select',
|
373 |
-
'label' => 'Mask Input',
|
374 |
-
'help_text' => 'Select a mask for the input field',
|
375 |
'options' => array(
|
376 |
array(
|
377 |
'value' => '',
|
378 |
-
'label' => 'None',
|
379 |
),
|
380 |
array(
|
381 |
'value' => '(000) 000-0000',
|
@@ -387,48 +346,48 @@ $element_customization_settings = array(
|
|
387 |
),
|
388 |
array(
|
389 |
'value' => '00/00/0000',
|
390 |
-
'label' => '23/03/2018',
|
391 |
),
|
392 |
array(
|
393 |
'value' => '00:00:00',
|
394 |
-
'label' => '23:59:59',
|
395 |
),
|
396 |
array(
|
397 |
'value' => '00/00/0000 00:00:00',
|
398 |
-
'label' => '23/03/2018 23:59:59',
|
399 |
),
|
400 |
array(
|
401 |
'value' => 'custom',
|
402 |
-
'label' => 'Custom',
|
403 |
)
|
404 |
),
|
405 |
),
|
406 |
'grid_columns' => array (
|
407 |
'template' => 'gridRowCols',
|
408 |
-
'label' => 'Grid Columns',
|
409 |
-
'help_text' => 'Write your own mask for this input',
|
410 |
),
|
411 |
'grid_rows' => array (
|
412 |
'template' => 'gridRowCols',
|
413 |
-
'label' => 'Grid Rows',
|
414 |
-
'help_text' => 'Write your own mask for this input',
|
415 |
),
|
416 |
'tabular_field_type' => array (
|
417 |
'template' => 'radio',
|
418 |
-
'label' => 'Field Type',
|
419 |
-
'help_text' => 'Field Type',
|
420 |
'options' => array(
|
421 |
array(
|
422 |
'value' => 'checkbox',
|
423 |
-
'label' => 'Checkbox',
|
424 |
),
|
425 |
array(
|
426 |
'value' => 'radio',
|
427 |
-
'label' => 'Radio',
|
428 |
),
|
429 |
)
|
430 |
),
|
431 |
|
432 |
);
|
433 |
|
434 |
-
return apply_filters( 'fluent_editor_element_customization_settings', $element_customization_settings );
|
15 |
exit;
|
16 |
}
|
17 |
|
18 |
+
$dateFormats = (new \FluentForm\App\Services\FormBuilder\Components\DateTime)->getAvailableDateFormats();
|
19 |
+
|
20 |
$element_customization_settings = array(
|
21 |
'name' => array(
|
22 |
'template' => 'nameAttr',
|
23 |
+
'label' => __('Name Attribute', 'fluentform'),
|
24 |
+
'help_text' => __('This is the field name attributes which is used to submit form data, name attribute must be unique.', 'fluentform'),
|
25 |
),
|
26 |
'label' => array(
|
27 |
'template' => 'inputText',
|
28 |
+
'label' => __('Element Label', 'fluentform'),
|
29 |
+
'help_text' => __('This is the field title the user will see when filling out the form.', 'fluentform'),
|
30 |
),
|
31 |
'label_placement' => array(
|
32 |
'template' => 'radioButton',
|
33 |
+
'label' => __('Label Placement', 'fluentform'),
|
34 |
+
'help_text' => __('Determine the position of label title where the user will see this. By choosing "Default", global label placement setting will be applied.', 'fluentform'),
|
35 |
'options' => array(
|
36 |
array(
|
37 |
'value' => '',
|
38 |
+
'label' => __('Default', 'fluentform'),
|
39 |
),
|
40 |
array(
|
41 |
'value' => 'top',
|
42 |
+
'label' => __('Top', 'fluentform'),
|
43 |
),
|
44 |
array(
|
45 |
'value' => 'left',
|
46 |
+
'label' => __('Left', 'fluentform'),
|
47 |
),
|
48 |
array(
|
49 |
'value' => 'right',
|
50 |
+
'label' => __('Right', 'fluentform'),
|
51 |
),
|
52 |
),
|
53 |
),
|
54 |
'button_style' => array (
|
55 |
'template' => 'selectBtnStyle',
|
56 |
+
'label' => __('Button Style', 'fluentform'),
|
57 |
+
'help_text' => __('Select a button style from the dropdown', 'fluentform'),
|
58 |
),
|
59 |
'button_size' => array (
|
60 |
'template' => 'radioButton',
|
61 |
+
'label' => __('Button Size', 'fluentform'),
|
62 |
+
'help_text' => __('Define a size of the button', 'fluentform'),
|
63 |
'options' => array(
|
64 |
array(
|
65 |
'value' => 'sm',
|
66 |
+
'label' => __('Small', 'fluentform'),
|
67 |
),
|
68 |
array(
|
69 |
'value' => 'md',
|
70 |
+
'label' => __('Medium', 'fluentform'),
|
71 |
),
|
72 |
array(
|
73 |
'value' => 'lg',
|
74 |
+
'label' => __('Large', 'fluentform'),
|
75 |
),
|
76 |
)
|
77 |
),
|
78 |
'placeholder' => array(
|
79 |
'template' => 'inputText',
|
80 |
+
'label' => __('Placeholder', 'fluentform'),
|
81 |
+
'help_text' => __('This is the field placeholder, the user will see this if the input field is empty.', 'fluentform'),
|
82 |
),
|
83 |
'date_format' => array(
|
84 |
'template' => 'select',
|
85 |
+
'label' => __('Date Format', 'fluentform'),
|
86 |
+
'filterable' => true,
|
87 |
+
'creatable' => true,
|
88 |
+
'placeholder' => __('Select Date Format', 'fluentform'),
|
89 |
+
'help_text' => __('Select any date format from the dropdown. The user will be able to choose a date in this given format.', 'fluentform'),
|
90 |
+
'options' => $dateFormats,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
),
|
92 |
'rows' => array(
|
93 |
'template' => 'inputText',
|
94 |
+
'label' => __('Rows', 'fluentform'),
|
95 |
+
'help_text' => __('How many rows will textarea take in a form. It\'s an HTML attributes for browser support.', 'fluentform'),
|
96 |
),
|
97 |
'cols' => array(
|
98 |
'template' => 'inputText',
|
99 |
+
'label' => __('Columns', 'fluentform'),
|
100 |
+
'help_text' => __('How many cols will textarea take in a form. It\'s an HTML attributes for browser support.', 'fluentform'),
|
101 |
),
|
102 |
'options' => array(
|
103 |
'template' => 'selectOptions',
|
104 |
+
'label' => __('Options', 'fluentform'),
|
105 |
+
'help_text' => __('Create options for the field and checkmark them for default selection.', 'fluentform'),
|
106 |
),
|
107 |
'validation_rules' => array(
|
108 |
'template' => 'validationRulesForm',
|
109 |
+
'label' => __('Validation Rules', 'fluentform'),
|
110 |
'help_text' => '',
|
111 |
),
|
112 |
'tnc_html' => array(
|
113 |
'template' => 'inputTextarea',
|
114 |
+
'label' => __('Terms & Conditions', 'fluentform'),
|
115 |
+
'help_text' => __('Write HTML content for terms & condition checkbox', 'fluentform'),
|
116 |
'rows' => 5,
|
117 |
'cols' => 3,
|
118 |
),
|
119 |
'hook_name' => array(
|
120 |
'template' => 'customHookName',
|
121 |
+
'label' => __('Hook Name', 'fluentform'),
|
122 |
+
'help_text' => __('WordPress Hook name to hook something in this place.', 'fluentform'),
|
123 |
),
|
124 |
'has_checkbox' => array(
|
125 |
'template' => 'inputCheckbox',
|
126 |
'options' => array(
|
127 |
array(
|
128 |
'value' => true,
|
129 |
+
'label' => __('Show Checkbox', 'fluentform'),
|
130 |
),
|
131 |
),
|
132 |
),
|
134 |
'template' => 'inputTextarea',
|
135 |
'rows' => 4,
|
136 |
'cols' => 2,
|
137 |
+
'label' => __('HTML Code', 'fluentform'),
|
138 |
+
'help_text' => __('Your valid HTML code will be shown to the user as normal content.', 'fluentform'),
|
139 |
),
|
140 |
'description' => array(
|
141 |
'template' => 'inputTextarea',
|
142 |
'rows' => 4,
|
143 |
'cols' => 2,
|
144 |
+
'label' => __('Description', 'fluentform'),
|
145 |
+
'help_text' => __('Description will be shown to the user as normal text content.', 'fluentform'),
|
146 |
),
|
147 |
'btn_text' => array(
|
148 |
'template' => 'inputText',
|
149 |
+
'label' => __('Button Text', 'fluentform'),
|
150 |
+
'help_text' => __('This will be visible as button text for upload file.', 'fluentform'),
|
151 |
),
|
152 |
'button_ui' => array(
|
153 |
'template' => 'prevNextButton',
|
154 |
+
'label' => __('Submit Button', 'fluentform'),
|
155 |
+
'help_text' => __('This is form submission button.', 'fluentform'),
|
156 |
),
|
157 |
'align' => array(
|
158 |
'template' => 'radio',
|
159 |
+
'label' => __('Content Alignment', 'fluentform'),
|
160 |
+
'help_text' => __('How the content will be aligned.', 'fluentform'),
|
161 |
'options' => array(
|
162 |
array(
|
163 |
'value' => 'left',
|
164 |
+
'label' => __('Left', 'fluentform'),
|
165 |
),
|
166 |
array(
|
167 |
'value' => 'center',
|
168 |
+
'label' => __('Center', 'fluentform'),
|
169 |
),
|
170 |
array(
|
171 |
'value' => 'right',
|
172 |
+
'label' => __('Right', 'fluentform'),
|
173 |
),
|
174 |
),
|
175 |
),
|
176 |
'shortcode' => array(
|
177 |
'template' => 'inputText',
|
178 |
+
'label' => __('Shortcode', 'fluentform'),
|
179 |
+
'help_text' => __('Your shortcode to render desired content in current place.', 'fluentform'),
|
180 |
),
|
181 |
'apply_styles' => array (
|
182 |
'template' => 'radioButton',
|
183 |
+
'label' => __('Apply Styles', 'fluentform'),
|
184 |
+
'help_text' => __('Apply styles provided here', 'fluentform'),
|
185 |
'options' => array(
|
186 |
array(
|
187 |
'value' => true,
|
188 |
+
'label' => __('Yes', 'fluentform'),
|
189 |
),
|
190 |
array(
|
191 |
'value' => false,
|
192 |
+
'label' => __('No', 'fluentform'),
|
193 |
)
|
194 |
),
|
195 |
),
|
196 |
'step_title' => array(
|
197 |
'template' => 'inputText',
|
198 |
+
'label' => __('Step Title', 'fluentform'),
|
199 |
+
'help_text' => __('Form step titles, user will see each title in each step.', 'fluentform'),
|
200 |
),
|
201 |
'progress_indicator' => array(
|
202 |
'template' => 'radio',
|
203 |
+
'label' => __('Progress Indicator', 'fluentform'),
|
204 |
+
'help_text' => __('Select any of them below, user will see progress of form steps according to your choice.', 'fluentform'),
|
205 |
'options' => array(
|
206 |
array(
|
207 |
'value' => 'progress-bar',
|
208 |
+
'label' => __('Progress Bar', 'fluentform'),
|
209 |
),
|
210 |
array(
|
211 |
'value' => 'steps',
|
212 |
+
'label' => __('Steps', 'fluentform'),
|
213 |
),
|
214 |
array(
|
215 |
'value' => '',
|
216 |
+
'label' => __('None', 'fluentform'),
|
217 |
),
|
218 |
),
|
219 |
),
|
220 |
'step_titles' => array(
|
221 |
'template' => 'customStepTitles',
|
222 |
+
'label' => __('Step Titles', 'fluentform'),
|
223 |
+
'help_text' => __('Form step titles, user will see each title in each step.', 'fluentform'),
|
224 |
),
|
225 |
'prev_btn' => array(
|
226 |
'template' => 'prevNextButton',
|
227 |
+
'label' => __('Previous Button', 'fluentform'),
|
228 |
+
'help_text' => __('Multi-step form\'s previous button', 'fluentform'),
|
229 |
),
|
230 |
'next_btn' => array(
|
231 |
'template' => 'prevNextButton',
|
232 |
+
'label' => __('Next Button', 'fluentform'),
|
233 |
+
'help_text' => __('Multi-step form\'s next button', 'fluentform'),
|
234 |
),
|
235 |
'address_fields' => array(
|
236 |
'template' => 'addressFields',
|
237 |
+
'label' => __('Address Fields', 'fluentform'),
|
238 |
),
|
239 |
'name_fields' => array(
|
240 |
'template' => 'nameFields',
|
241 |
+
'label' => __('Name Fields', 'fluentform'),
|
242 |
),
|
243 |
'multi_column' => array(
|
244 |
'template' => 'inputCheckbox',
|
245 |
'options' => array(
|
246 |
array(
|
247 |
'value' => true,
|
248 |
+
'label' => __('Enable Multiple Columns', 'fluentform'),
|
249 |
),
|
250 |
),
|
251 |
),
|
252 |
'repeat_fields' => array(
|
253 |
'template' => 'customRepeatFields',
|
254 |
+
'label' => __('Repeat Fields', 'fluentform'),
|
255 |
+
'help_text' => __('This is a form field which a user will be able to repeat.', 'fluentform'),
|
256 |
),
|
257 |
'admin_field_label' => array(
|
258 |
'template' => 'inputText',
|
259 |
+
'label' => __('Admin Field Label', 'fluentform'),
|
260 |
+
'help_text' => __('Admin field label is field title which will be used for admin field title.', 'fluentform'),
|
261 |
),
|
262 |
'value' => array(
|
263 |
'template' => 'inputValue',
|
264 |
+
'label' => __('Default Value', 'fluentform'),
|
265 |
+
'help_text' => __('If you would like to pre-populate the value of a field, enter it here.', 'fluentform'),
|
266 |
),
|
267 |
'container_class' => array(
|
268 |
'template' => 'inputText',
|
269 |
+
'label' => __('Container Class', 'fluentform'),
|
270 |
+
'help_text' => __('Class for the field wrapper. This can be used to style current element.', 'fluentform'),
|
271 |
),
|
272 |
'class' => array(
|
273 |
'template' => 'inputText',
|
274 |
+
'label' => __('Element Class', 'fluentform'),
|
275 |
+
'help_text' => __('Class for the field. This can be used to style current element.', 'fluentform'),
|
276 |
),
|
277 |
'country_list' => array(
|
278 |
'template' => 'customCountryList',
|
279 |
+
'label' => __('Country List', 'fluentform'),
|
280 |
),
|
281 |
'product_field_types' => array(
|
282 |
'template' => 'productFieldTypes',
|
283 |
+
'label' => __('Options', 'fluentform'),
|
284 |
),
|
285 |
'help_message' => array(
|
286 |
'template' => 'inputText',
|
287 |
+
'label' => __('Help Message', 'fluentform'),
|
288 |
+
'help_text' => __('Help message will be shown as tooltip next to sidebar or below the field.', 'fluentform'),
|
289 |
),
|
290 |
'conditional_logics' => array(
|
291 |
'template' => 'conditionalLogics',
|
292 |
+
'label' => __('Conditional Logic', 'fluentform'),
|
293 |
+
'help_text' => __('Create rules to dynamically display or hide this field based on values from another field.', 'fluentform'),
|
294 |
+
),
|
|
|
|
|
|
|
|
|
|
|
295 |
'background_color' => array(
|
296 |
'template' => 'inputColor',
|
297 |
+
'label' => __('Background Color', 'fluentform'),
|
298 |
+
'help_text' => __('The Background color of the element', 'fluentform')
|
299 |
),
|
300 |
'color' => array(
|
301 |
'template' => 'inputColor',
|
302 |
+
'label' => __('Font Color', 'fluentform'),
|
303 |
+
'help_text' => __('Font color of the element', 'fluentform')
|
304 |
),
|
305 |
'show_text' => array(
|
306 |
'template' => 'radio',
|
307 |
+
'label' => __('Show Text?', 'fluentform'),
|
308 |
+
'help_text' => __('Show Text beside the ratings', 'fluentform'),
|
309 |
'options' => array(
|
310 |
array(
|
311 |
'value' => true,
|
312 |
+
'label' => __('Yes', 'fluentform'),
|
313 |
),
|
314 |
array(
|
315 |
'value' => false,
|
316 |
+
'label' => __('No', 'fluentform'),
|
317 |
)
|
318 |
),
|
319 |
),
|
320 |
'data-mask' => array (
|
321 |
'template' => 'customMask',
|
322 |
+
'label' => __('Custom Mask', 'fluentform'),
|
323 |
+
'help_text' => __('Write your own mask for this input', 'fluentform'),
|
324 |
'dependency' => array (
|
325 |
'depends_on' => 'settings/temp_mask',
|
326 |
'value' => 'custom',
|
329 |
),
|
330 |
'temp_mask' => array (
|
331 |
'template' => 'select',
|
332 |
+
'label' => __('Mask Input', 'fluentform'),
|
333 |
+
'help_text' => __('Select a mask for the input field', 'fluentform'),
|
334 |
'options' => array(
|
335 |
array(
|
336 |
'value' => '',
|
337 |
+
'label' => __('None', 'fluentform'),
|
338 |
),
|
339 |
array(
|
340 |
'value' => '(000) 000-0000',
|
346 |
),
|
347 |
array(
|
348 |
'value' => '00/00/0000',
|
349 |
+
'label' => __('23/03/2018', 'fluentform'),
|
350 |
),
|
351 |
array(
|
352 |
'value' => '00:00:00',
|
353 |
+
'label' => __('23:59:59', 'fluentform'),
|
354 |
),
|
355 |
array(
|
356 |
'value' => '00/00/0000 00:00:00',
|
357 |
+
'label' => __('23/03/2018 23:59:59', 'fluentform'),
|
358 |
),
|
359 |
array(
|
360 |
'value' => 'custom',
|
361 |
+
'label' => __('Custom', 'fluentform'),
|
362 |
)
|
363 |
),
|
364 |
),
|
365 |
'grid_columns' => array (
|
366 |
'template' => 'gridRowCols',
|
367 |
+
'label' => __('Grid Columns', 'fluentform'),
|
368 |
+
'help_text' => __('Write your own mask for this input', 'fluentform'),
|
369 |
),
|
370 |
'grid_rows' => array (
|
371 |
'template' => 'gridRowCols',
|
372 |
+
'label' => __('Grid Rows', 'fluentform'),
|
373 |
+
'help_text' => __('Write your own mask for this input', 'fluentform'),
|
374 |
),
|
375 |
'tabular_field_type' => array (
|
376 |
'template' => 'radio',
|
377 |
+
'label' => __('Field Type', 'fluentform'),
|
378 |
+
'help_text' => __('Field Type', 'fluentform'),
|
379 |
'options' => array(
|
380 |
array(
|
381 |
'value' => 'checkbox',
|
382 |
+
'label' => __('Checkbox', 'fluentform'),
|
383 |
),
|
384 |
array(
|
385 |
'value' => 'radio',
|
386 |
+
'label' => __('Radio', 'fluentform'),
|
387 |
),
|
388 |
)
|
389 |
),
|
390 |
|
391 |
);
|
392 |
|
393 |
+
return apply_filters( 'fluent_editor_element_customization_settings', $element_customization_settings );
|
app/Services/FormBuilder/ElementSettingsPlacement.php
CHANGED
@@ -369,13 +369,13 @@ $element_settings_placement = array(
|
|
369 |
'generalExtras' => array(
|
370 |
'btn_text' => array(
|
371 |
'template' => 'inputText',
|
372 |
-
'label' => 'Button Text',
|
373 |
-
'help_text' => 'Form submission button text.',
|
374 |
),
|
375 |
'background_color' => array(
|
376 |
'template' => 'inputColor',
|
377 |
-
'label' => 'Background Color',
|
378 |
-
'help_text' => 'The Background color of the element',
|
379 |
'dependency' => array (
|
380 |
'depends_on' => 'settings/button_style',
|
381 |
'value' => '',
|
@@ -384,8 +384,8 @@ $element_settings_placement = array(
|
|
384 |
),
|
385 |
'color' => array(
|
386 |
'template' => 'inputColor',
|
387 |
-
'label' => 'Font Color',
|
388 |
-
'help_text' => 'Font color of the element',
|
389 |
'dependency' => array (
|
390 |
'depends_on' => 'settings/button_style',
|
391 |
'value' => '',
|
@@ -453,8 +453,8 @@ $element_settings_placement = array(
|
|
453 |
'generalExtras' => array(
|
454 |
'tnc_html' => array(
|
455 |
'template' => 'inputTextarea',
|
456 |
-
'label' => 'Description',
|
457 |
-
'help_text' => 'Write HTML content for GDPR agreement checkbox',
|
458 |
'rows' => 5,
|
459 |
'cols' => 3,
|
460 |
)
|
@@ -492,4 +492,4 @@ $element_settings_placement = array(
|
|
492 |
),
|
493 |
);
|
494 |
|
495 |
-
return apply_filters( 'fluent_editor_element_settings_placement', $element_settings_placement );
|
369 |
'generalExtras' => array(
|
370 |
'btn_text' => array(
|
371 |
'template' => 'inputText',
|
372 |
+
'label' => __('Button Text', 'fluentform'),
|
373 |
+
'help_text' => __('Form submission button text.', 'fluentform'),
|
374 |
),
|
375 |
'background_color' => array(
|
376 |
'template' => 'inputColor',
|
377 |
+
'label' => __('Background Color', 'fluentform'),
|
378 |
+
'help_text' => __('The Background color of the element', 'fluentform'),
|
379 |
'dependency' => array (
|
380 |
'depends_on' => 'settings/button_style',
|
381 |
'value' => '',
|
384 |
),
|
385 |
'color' => array(
|
386 |
'template' => 'inputColor',
|
387 |
+
'label' => __('Font Color', 'fluentform'),
|
388 |
+
'help_text' => __('Font color of the element', 'fluentform'),
|
389 |
'dependency' => array (
|
390 |
'depends_on' => 'settings/button_style',
|
391 |
'value' => '',
|
453 |
'generalExtras' => array(
|
454 |
'tnc_html' => array(
|
455 |
'template' => 'inputTextarea',
|
456 |
+
'label' => __('Description', 'fluentform'),
|
457 |
+
'help_text' => __('Write HTML content for GDPR agreement checkbox', 'fluentform'),
|
458 |
'rows' => 5,
|
459 |
'cols' => 3,
|
460 |
)
|
492 |
),
|
493 |
);
|
494 |
|
495 |
+
return apply_filters( 'fluent_editor_element_settings_placement', $element_settings_placement );
|
app/Services/FormBuilder/FormBuilder.php
CHANGED
@@ -41,7 +41,8 @@ class FormBuilder
|
|
41 |
$labelPlacement = $form->settings['layout']['labelPlacement'];
|
42 |
ob_start();
|
43 |
echo "<div class='fluentform'>";
|
44 |
-
|
|
|
45 |
echo "<input type='hidden' name='__fluent_form_embded_post_id' value='".get_the_ID()."' />";
|
46 |
|
47 |
wp_nonce_field('fluentform-submit-form', '_fluentform_'.$form->id.'_fluentformnonce', true, true);
|
@@ -65,7 +66,7 @@ class FormBuilder
|
|
65 |
|
66 |
$this->app->doAction('fluentform_render_item_submit_button', $form->fields['submitButton'], $form);
|
67 |
echo "</form><div id='fluentform_".$form->id."_errors' class='ff-errors-in-stack'></div></div>";
|
68 |
-
|
69 |
return ob_get_clean();
|
70 |
}
|
71 |
|
41 |
$labelPlacement = $form->settings['layout']['labelPlacement'];
|
42 |
ob_start();
|
43 |
echo "<div class='fluentform'>";
|
44 |
+
do_action('fluentform_before_form_render', $form);
|
45 |
+
echo "<form data-form_id={$form->id} id=fluentform_{$form->id} class='frm-fluent-form fluent_form_{$form->id} ff-el-form-{$labelPlacement}'>";
|
46 |
echo "<input type='hidden' name='__fluent_form_embded_post_id' value='".get_the_ID()."' />";
|
47 |
|
48 |
wp_nonce_field('fluentform-submit-form', '_fluentform_'.$form->id.'_fluentformnonce', true, true);
|
66 |
|
67 |
$this->app->doAction('fluentform_render_item_submit_button', $form->fields['submitButton'], $form);
|
68 |
echo "</form><div id='fluentform_".$form->id."_errors' class='ff-errors-in-stack'></div></div>";
|
69 |
+
do_action('fluentform_after_form_render', $form);
|
70 |
return ob_get_clean();
|
71 |
}
|
72 |
|
app/Services/FormBuilder/MessageShortCodeParser.php
DELETED
@@ -1,192 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Services\FormBuilder;
|
4 |
-
|
5 |
-
use FluentForm\App;
|
6 |
-
use FluentForm\App\Modules\Form\FormDataParser;
|
7 |
-
use FluentForm\App\Modules\Form\FormFieldsParser;
|
8 |
-
use FluentForm\Framework\Helpers\ArrayHelper;
|
9 |
-
|
10 |
-
class MessageShortCodeParser
|
11 |
-
{
|
12 |
-
protected static $data = null;
|
13 |
-
|
14 |
-
/**
|
15 |
-
* Parse Norifications
|
16 |
-
* @param array $notifications
|
17 |
-
* @param int $insertId
|
18 |
-
* @param array $data
|
19 |
-
* @param object $form
|
20 |
-
* @return array $notifications
|
21 |
-
*/
|
22 |
-
public static function parseMessageShortCode($notifications, $insertId, $data, $form, $cache = true)
|
23 |
-
{
|
24 |
-
if ($cache && !is_null(static::$data)) {
|
25 |
-
return static::$data;
|
26 |
-
}
|
27 |
-
|
28 |
-
$formattedProperties = array();
|
29 |
-
|
30 |
-
$parsableFields = array(
|
31 |
-
'fromName',
|
32 |
-
'fromEmail',
|
33 |
-
'replyTo',
|
34 |
-
'bcc',
|
35 |
-
'subject',
|
36 |
-
'message'
|
37 |
-
);
|
38 |
-
|
39 |
-
list(
|
40 |
-
$formProperties,
|
41 |
-
$userProperties,
|
42 |
-
$postProperties,
|
43 |
-
$others
|
44 |
-
) = static::parsePropertiesFor($parsableFields, $notifications);
|
45 |
-
|
46 |
-
static::setOtherProperties($formattedProperties, $others, $data, $form, $insertId);
|
47 |
-
static::setUserProperties($formattedProperties, $userProperties, $data, $form, $insertId);
|
48 |
-
static::setPostProperties($formattedProperties, $postProperties, $data, $form, $insertId);
|
49 |
-
static::setFormProperties($formattedProperties, $formProperties, $data, $form, $insertId);
|
50 |
-
|
51 |
-
// If any property is array then make it string
|
52 |
-
foreach ($formattedProperties as $key => $formattedProperty) {
|
53 |
-
if(is_array($formattedProperty)) {
|
54 |
-
$formattedProperties[$key] = implode(', ',$formattedProperty);
|
55 |
-
}
|
56 |
-
}
|
57 |
-
|
58 |
-
foreach ($notifications as &$notification) {
|
59 |
-
if (isset($notification['sendTo']) && $notification['sendTo']['type'] == 'field') {
|
60 |
-
$notification['sendTo']['email'] = $formattedProperties['inputs.email'];
|
61 |
-
}
|
62 |
-
|
63 |
-
foreach ($parsableFields as $fieldName) {
|
64 |
-
if (isset($notification[$fieldName])) {
|
65 |
-
foreach ($formattedProperties as $key => $value) {
|
66 |
-
$notification[$fieldName] = str_replace(
|
67 |
-
'{'.$key.'}', $value, $notification[$fieldName]
|
68 |
-
);
|
69 |
-
}
|
70 |
-
}
|
71 |
-
}
|
72 |
-
}
|
73 |
-
|
74 |
-
return $cache ? (static::$data = $notifications) : $notifications;
|
75 |
-
}
|
76 |
-
|
77 |
-
public static function parsePropertiesFor($parsableFields, $notifications)
|
78 |
-
{
|
79 |
-
$totalString = '';
|
80 |
-
foreach ($notifications as $notification) {
|
81 |
-
if (isset($notification['sendTo']) && $notification['sendTo']['type'] == 'field') {
|
82 |
-
$totalString .= '{inputs.'.$notification['sendTo']['field'].'}';
|
83 |
-
}
|
84 |
-
foreach ($parsableFields as $fieldName) {
|
85 |
-
if (isset($notification[$fieldName])) {
|
86 |
-
$totalString .= $notification[$fieldName];
|
87 |
-
}
|
88 |
-
}
|
89 |
-
}
|
90 |
-
|
91 |
-
preg_match_all('/{(.*?)}/', $totalString, $matches);
|
92 |
-
$uniqueMatches = array_unique($matches[1]);
|
93 |
-
$replacables = array_unique($matches[0]);
|
94 |
-
$formProperties = $userProperties = $postProperties = $others = array();
|
95 |
-
foreach ($uniqueMatches as $match) {
|
96 |
-
if (strpos($match, 'user.') !== false) {
|
97 |
-
$userProperties[] = substr($match, strlen('user.'));
|
98 |
-
} elseif (strpos($match, 'inputs.') !== false) {
|
99 |
-
$formProperties[] = substr($match, strlen('inputs.'));
|
100 |
-
} elseif (strpos($match, 'embed_post.') !== false) {
|
101 |
-
$postProperties[] = substr($match, strlen('embed_post.'));
|
102 |
-
} else {
|
103 |
-
$others[] = $match;
|
104 |
-
}
|
105 |
-
}
|
106 |
-
|
107 |
-
return array($formProperties, $userProperties, $postProperties, $others);
|
108 |
-
}
|
109 |
-
|
110 |
-
public static function setFormProperties(&$formattedProperties, $formProperties, $data, $form, $insertId)
|
111 |
-
{
|
112 |
-
if (!$formProperties) return;
|
113 |
-
|
114 |
-
$formFields = FormFieldsParser::getShortCodeInputs($form, ['admin_label', 'attributes', 'options']);
|
115 |
-
|
116 |
-
foreach ($formProperties as $formProperty) {
|
117 |
-
$field = @$formFields[$formProperty];
|
118 |
-
|
119 |
-
$formattedProperties['inputs.'.$formProperty] = App::applyFilters(
|
120 |
-
'fluentform_response_render_'.$field['element'],
|
121 |
-
ArrayHelper::get($data, $formProperty, ''),
|
122 |
-
$field,
|
123 |
-
$form->id
|
124 |
-
);
|
125 |
-
}
|
126 |
-
}
|
127 |
-
|
128 |
-
public static function setUserProperties(&$formattedProperties, $userProperties, $data, $form, $insertId)
|
129 |
-
{
|
130 |
-
if (!$userProperties) return;
|
131 |
-
|
132 |
-
$user = wp_get_current_user();
|
133 |
-
foreach ($userProperties as $userProperty) {
|
134 |
-
$formattedProperties['user.'.$userProperty] = $user->{$userProperty};
|
135 |
-
}
|
136 |
-
}
|
137 |
-
|
138 |
-
public static function setPostProperties(&$formattedProperties, $postProperties, $data, $form, $insetId)
|
139 |
-
{
|
140 |
-
if (!$postProperties) return;
|
141 |
-
|
142 |
-
$embed_post_id = @$data['__fluent_form_embded_post_id'];
|
143 |
-
if($embed_post_id) {
|
144 |
-
$post = get_post($embed_post_id);
|
145 |
-
$post->permalink = get_the_permalink($post);
|
146 |
-
foreach ($postProperties as $post_property) {
|
147 |
-
$formattedProperties['embed_post.'.$post_property] = $post->{$post_property};
|
148 |
-
}
|
149 |
-
}
|
150 |
-
}
|
151 |
-
|
152 |
-
public static function setOtherProperties(&$formattedProperties, $others, $data, $form, $insertId)
|
153 |
-
{
|
154 |
-
if (!$others) return;
|
155 |
-
|
156 |
-
foreach ($others as $other) {
|
157 |
-
if ($other == 'date.m/d/Y') {
|
158 |
-
$formattedProperties[$other] = date('m/d/Y');
|
159 |
-
continue;
|
160 |
-
} elseif ($other == 'date.d/m/Y') {
|
161 |
-
$formattedProperties[$other] = date('d/m/Y');
|
162 |
-
continue;
|
163 |
-
} elseif ($other == 'browser.platform') {
|
164 |
-
$browser = new \FluentForm\App\Services\Browser\Browser();
|
165 |
-
$formattedProperties[$other] = $browser->getPlatform();
|
166 |
-
continue;
|
167 |
-
} elseif ($other == 'browser.name') {
|
168 |
-
$browser = new \FluentForm\App\Services\Browser\Browser();
|
169 |
-
$formattedProperties[$other] = $browser->getBrowser();
|
170 |
-
continue;
|
171 |
-
} elseif ($other == 'ip') {
|
172 |
-
$formattedProperties[$other] = App::make('request')->getIp();
|
173 |
-
continue;
|
174 |
-
} elseif ($other == 'admin_email') {
|
175 |
-
$formattedProperties[$other] = get_option('admin_email', false);
|
176 |
-
continue;
|
177 |
-
} else if($other == 'all_data') {
|
178 |
-
$formFields = FormFieldsParser::getEntryInputs($form);
|
179 |
-
$inputLabels = FormFieldsParser::getAdminLabels($form, $formFields);
|
180 |
-
$entry = wpFluent()->table('fluentform_submissions')->find($insertId);
|
181 |
-
$response = FormDataParser::parseFormSubmission($entry, $form, $formFields);
|
182 |
-
$html = '';
|
183 |
-
foreach ($inputLabels as $key => $label) {
|
184 |
-
if (array_key_exists($key, $response->user_inputs)) {
|
185 |
-
$html .= $label .': '. $response->user_inputs[$key] . '<br>';
|
186 |
-
}
|
187 |
-
}
|
188 |
-
$formattedProperties[$other] = $html;
|
189 |
-
}
|
190 |
-
}
|
191 |
-
}
|
192 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/FormBuilder/Notifications/AsyncEmailSender.php
DELETED
@@ -1,45 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Services\FormBuilder\Notifications;
|
4 |
-
|
5 |
-
use FluentForm\App\Services\WPAsync\WPAsyncRequest;
|
6 |
-
|
7 |
-
class AsyncEmailSender extends WPAsyncRequest
|
8 |
-
{
|
9 |
-
/**
|
10 |
-
* @var null
|
11 |
-
*/
|
12 |
-
protected $app = null;
|
13 |
-
|
14 |
-
/**
|
15 |
-
* @var string
|
16 |
-
*/
|
17 |
-
protected $action = 'fluentform_send_mail_async';
|
18 |
-
|
19 |
-
/**
|
20 |
-
* @var string
|
21 |
-
*/
|
22 |
-
protected $class = 'FluentForm\App\Services\FormBuilder\Notifications\EmailNotification';
|
23 |
-
|
24 |
-
public function __construct($app)
|
25 |
-
{
|
26 |
-
$this->app = $app;
|
27 |
-
parent::__construct();
|
28 |
-
}
|
29 |
-
|
30 |
-
/**
|
31 |
-
* Handle
|
32 |
-
*
|
33 |
-
* Override this method to perform any actions required
|
34 |
-
* during the async request.
|
35 |
-
*/
|
36 |
-
public function handle()
|
37 |
-
{
|
38 |
-
$data = $_POST['form_data'];
|
39 |
-
$formId = intval($_POST['form_id']);
|
40 |
-
$notification = wp_unslash($_POST['notification']);
|
41 |
-
$form = wpFluent()->table('fluentform_forms')->find($formId);
|
42 |
-
|
43 |
-
$this->app->make($this->class)->notify($notification, $data, $form);
|
44 |
-
}
|
45 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/FormBuilder/Notifications/EmailNotification.php
CHANGED
@@ -32,7 +32,7 @@ class EmailNotification
|
|
32 |
* @param StdClass $form [The form object from database]
|
33 |
* @return bool
|
34 |
*/
|
35 |
-
public function notify($notification, $submittedData, $form)
|
36 |
{
|
37 |
$headers = [
|
38 |
'Content-Type: text/html; charset=UTF-8'
|
@@ -67,6 +67,10 @@ class EmailNotification
|
|
67 |
$emailBody = $this->getEmailWithTemplate($emailBody, $form, $notification);
|
68 |
}
|
69 |
|
|
|
|
|
|
|
|
|
70 |
$isMailSentSuccessfully = wp_mail(
|
71 |
$notification['sendTo']['email'],
|
72 |
$notification['subject'],
|
@@ -77,6 +81,26 @@ class EmailNotification
|
|
77 |
|
78 |
$this->emptyTmp($attachments);
|
79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
return $isMailSentSuccessfully;
|
81 |
}
|
82 |
|
32 |
* @param StdClass $form [The form object from database]
|
33 |
* @return bool
|
34 |
*/
|
35 |
+
public function notify($notification, $submittedData, $form, $entryId = false)
|
36 |
{
|
37 |
$headers = [
|
38 |
'Content-Type: text/html; charset=UTF-8'
|
67 |
$emailBody = $this->getEmailWithTemplate($emailBody, $form, $notification);
|
68 |
}
|
69 |
|
70 |
+
if(!$notification['sendTo']['email'] || !$notification['subject']) {
|
71 |
+
return false;
|
72 |
+
}
|
73 |
+
|
74 |
$isMailSentSuccessfully = wp_mail(
|
75 |
$notification['sendTo']['email'],
|
76 |
$notification['subject'],
|
81 |
|
82 |
$this->emptyTmp($attachments);
|
83 |
|
84 |
+
if($entryId) {
|
85 |
+
if($isMailSentSuccessfully) {
|
86 |
+
$message = 'Email Notification sent to '.$notification['sendTo']['email'].' and the subject: '.$notification['subject'];
|
87 |
+
} else {
|
88 |
+
$message = 'Email Notification may failed to deliver to '.$notification['sendTo']['email'].' and the subject: '.$notification['subject'].', Please check your SMTP/wp_mail functionality/configration';
|
89 |
+
}
|
90 |
+
|
91 |
+
wpFluent()->table('fluentform_submission_meta')
|
92 |
+
->insert([
|
93 |
+
'response_id' => $entryId,
|
94 |
+
'form_id' => $form->id,
|
95 |
+
'meta_key' => 'api_log',
|
96 |
+
'value' => $message,
|
97 |
+
'status' => 'info',
|
98 |
+
'name' => 'FluentForm Bot',
|
99 |
+
'created_at' => date('Y-m-d H:i:s'),
|
100 |
+
'updated_at' => date('Y-m-d H:i:s')
|
101 |
+
]);
|
102 |
+
}
|
103 |
+
|
104 |
return $isMailSentSuccessfully;
|
105 |
}
|
106 |
|
app/Services/FormBuilder/Notifications/EmailNotificationActions.php
CHANGED
@@ -57,7 +57,7 @@ class EmailNotificationActions
|
|
57 |
$entryId
|
58 |
);
|
59 |
|
60 |
-
$notifier->notify($notification, $data, $form);
|
61 |
}
|
62 |
}
|
63 |
}
|
@@ -74,7 +74,8 @@ class EmailNotificationActions
|
|
74 |
|
75 |
public function handleNotification($entryId, $data, $form)
|
76 |
{
|
77 |
-
|
|
|
78 |
return $this->app->doAction('fluentform_send_email_async', $entryId, $data, $form);
|
79 |
}
|
80 |
return $this->app->doAction('fluentform_send_email', $entryId, $data, $form);
|
57 |
$entryId
|
58 |
);
|
59 |
|
60 |
+
$notifier->notify($notification, $data, $form, $entryId);
|
61 |
}
|
62 |
}
|
63 |
}
|
74 |
|
75 |
public function handleNotification($entryId, $data, $form)
|
76 |
{
|
77 |
+
// To send the email on asynchronously, we need to change the false to true or add a filter for it.
|
78 |
+
if ($this->app->applyFilters('fluentform_sending_email_async', false, $entryId, $data, $form)) {
|
79 |
return $this->app->doAction('fluentform_send_email_async', $entryId, $data, $form);
|
80 |
}
|
81 |
return $this->app->doAction('fluentform_send_email', $entryId, $data, $form);
|
app/Services/FormBuilder/ShortCodeParser.php
CHANGED
@@ -98,6 +98,9 @@ class ShortCodeParser
|
|
98 |
} elseif (strpos($matches[1], 'embed_post.') !== false) {
|
99 |
$postProperty = substr($matches[1], strlen('embed_post.'));
|
100 |
return static::getPostData($postProperty);
|
|
|
|
|
|
|
101 |
} else {
|
102 |
return static::getOtherData($matches[1]);
|
103 |
}
|
@@ -149,6 +152,20 @@ class ShortCodeParser
|
|
149 |
return static::$store['post']->{$key};
|
150 |
}
|
151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
protected static function getOtherData($key)
|
153 |
{
|
154 |
if ($key == 'date.d/m/Y') {
|
@@ -170,7 +187,7 @@ class ShortCodeParser
|
|
170 |
$html = '<table width="600" cellpadding="0" cellspacing="0"><tbody>';
|
171 |
foreach ($inputLabels as $key => $label) {
|
172 |
if (array_key_exists($key, $response->user_inputs)) {
|
173 |
-
$html .= '<tr class="field-label"><th style="padding: 6px 12px; background-color: #f8f8f8; text-align: left;"><strong>'
|
174 |
}
|
175 |
}
|
176 |
$html .= '</tbody></table>';
|
98 |
} elseif (strpos($matches[1], 'embed_post.') !== false) {
|
99 |
$postProperty = substr($matches[1], strlen('embed_post.'));
|
100 |
return static::getPostData($postProperty);
|
101 |
+
} elseif (strpos($matches[1], 'wp.') !== false) {
|
102 |
+
$wpProperty = substr($matches[1], strlen('wp.'));
|
103 |
+
return static::getWPData($wpProperty);
|
104 |
} else {
|
105 |
return static::getOtherData($matches[1]);
|
106 |
}
|
152 |
return static::$store['post']->{$key};
|
153 |
}
|
154 |
|
155 |
+
protected static function getWPData($key)
|
156 |
+
{
|
157 |
+
if($key == 'admin_email') {
|
158 |
+
return get_option('admin_email');
|
159 |
+
}
|
160 |
+
if($key == 'site_url') {
|
161 |
+
return site_url();
|
162 |
+
}
|
163 |
+
if($key == 'site_title') {
|
164 |
+
return get_option('blogname');
|
165 |
+
}
|
166 |
+
return $key;
|
167 |
+
}
|
168 |
+
|
169 |
protected static function getOtherData($key)
|
170 |
{
|
171 |
if ($key == 'date.d/m/Y') {
|
187 |
$html = '<table width="600" cellpadding="0" cellspacing="0"><tbody>';
|
188 |
foreach ($inputLabels as $key => $label) {
|
189 |
if (array_key_exists($key, $response->user_inputs)) {
|
190 |
+
$html .= '<tr class="field-label"><th style="padding: 6px 12px; background-color: #f8f8f8; text-align: left;"><strong>' . $label . '</strong></th></tr><tr class="field-value"><td style="padding: 6px 12px 12px 12px;">' . ArrayHelper::get($response->user_inputs, $key) . '</td></tr>';
|
191 |
}
|
192 |
}
|
193 |
$html .= '</tbody></table>';
|
app/Services/FormBuilder/ValidationRuleSettings.php
CHANGED
@@ -19,17 +19,17 @@ $validation_rule_settings = array(
|
|
19 |
'required' =>
|
20 |
array(
|
21 |
'template' => 'inputRadio',
|
22 |
-
'label' => 'Required',
|
23 |
-
'help_text' => 'Select whether this field is a required field for the form or not.',
|
24 |
'options' =>
|
25 |
array(
|
26 |
array(
|
27 |
'value' => true,
|
28 |
-
'label' => 'Yes',
|
29 |
),
|
30 |
array(
|
31 |
'value' => false,
|
32 |
-
'label' => 'No',
|
33 |
),
|
34 |
),
|
35 |
),
|
@@ -37,46 +37,46 @@ $validation_rule_settings = array(
|
|
37 |
array(
|
38 |
'template' => 'inputText',
|
39 |
'type' => 'number',
|
40 |
-
'label' => 'Min Value',
|
41 |
-
'help_text' => 'Minimum value for the input field.',
|
42 |
),
|
43 |
'max' =>
|
44 |
array(
|
45 |
'template' => 'inputText',
|
46 |
'type' => 'number',
|
47 |
-
'label' => 'Max Value',
|
48 |
-
'help_text' => 'Maximum value for the input field.',
|
49 |
),
|
50 |
'max_file_size' =>
|
51 |
array(
|
52 |
'template' => 'maxFileSize',
|
53 |
-
'label' => 'Max File Size',
|
54 |
-
'help_text' => 'The file size limit uploaded by the user.',
|
55 |
),
|
56 |
'max_file_count' =>
|
57 |
array(
|
58 |
'template' => 'inputText',
|
59 |
'type' => 'number',
|
60 |
-
'label' => 'Max Files Count',
|
61 |
-
'help_text' => 'Maximum user file upload number.',
|
62 |
),
|
63 |
'allowed_file_types' =>
|
64 |
array(
|
65 |
'template' => 'inputCheckbox',
|
66 |
-
'label' => 'Allowed Files',
|
67 |
-
'help_text' => 'Allowed Files',
|
68 |
'fileTypes' =>
|
69 |
array(
|
70 |
array(
|
71 |
-
'title' => 'Images',
|
72 |
'types' => array( 'jpg', 'jpeg', 'gif', 'png', 'bmp' ),
|
73 |
),
|
74 |
array(
|
75 |
-
'title' => 'Audio',
|
76 |
'types' => array( 'mp3', 'wav', 'ogg', 'wma', 'mka', 'm4a', 'ra', 'mid', 'midi' ),
|
77 |
),
|
78 |
array(
|
79 |
-
'title' => 'Video',
|
80 |
'types' => array(
|
81 |
'avi',
|
82 |
'divx',
|
@@ -93,11 +93,11 @@ $validation_rule_settings = array(
|
|
93 |
),
|
94 |
),
|
95 |
array(
|
96 |
-
'title' => 'PDF',
|
97 |
'types' => array( 'pdf' ),
|
98 |
),
|
99 |
array(
|
100 |
-
'title' => 'Docs',
|
101 |
'types' => array(
|
102 |
'doc',
|
103 |
'ppt',
|
@@ -119,50 +119,50 @@ $validation_rule_settings = array(
|
|
119 |
),
|
120 |
),
|
121 |
array(
|
122 |
-
'title' => 'Zip Archives',
|
123 |
'types' => array( 'zip', 'gz', 'gzip', 'rar', '7z', ),
|
124 |
),
|
125 |
array(
|
126 |
-
'title' => 'Executable Files',
|
127 |
'types' =>
|
128 |
array( 'exe' ),
|
129 |
),
|
130 |
array(
|
131 |
-
'title' => 'CSV',
|
132 |
'types' => array( 'csv' ),
|
133 |
),
|
134 |
),
|
135 |
'options' => array(
|
136 |
array(
|
137 |
-
'label' => 'Images (jpg, jpeg, gif, png, bmp)',
|
138 |
'value' => 'jpg|jpeg|gif|png|bmp',
|
139 |
),
|
140 |
array(
|
141 |
-
'label' => 'Audio (mp3, wav, ogg, wma, mka, m4a, ra, mid, midi)',
|
142 |
'value' => 'mp3|wav|ogg|wma|mka|m4a|ra|mid|midi',
|
143 |
),
|
144 |
array(
|
145 |
-
'label' => 'Video (avi, divx, flv, mov, ogv, mkv, mp4, m4v, divx, mpg, mpeg, mpe)',
|
146 |
'value' => 'avi|divx|flv|mov|ogv|mkv|mp4|m4v|divx|mpg|mpeg|mpe|video/quicktime|qt',
|
147 |
),
|
148 |
array(
|
149 |
-
'label' => 'PDF (pdf)',
|
150 |
'value' => 'pdf',
|
151 |
),
|
152 |
array(
|
153 |
-
'label' => 'Docs (doc, ppt, pps, xls, mdb, docx, xlsx, pptx, odt, odp, ods, odg, odc, odb, odf, rtf, txt)',
|
154 |
'value' => 'doc|ppt|pps|xls|mdb|docx|xlsx|pptx|odt|odp|ods|odg|odc|odb|odf|rtf|txt',
|
155 |
),
|
156 |
array(
|
157 |
-
'label' => 'Zip Archives (zip, gz, gzip, rar, 7z)',
|
158 |
'value' => 'zip|gz|gzip|rar|7z',
|
159 |
),
|
160 |
array(
|
161 |
-
'label' => 'Executable Files (exe)',
|
162 |
'value' => 'exe',
|
163 |
),
|
164 |
array(
|
165 |
-
'label' => 'CSV (csv)',
|
166 |
'value' => 'csv',
|
167 |
),
|
168 |
),
|
@@ -170,38 +170,38 @@ $validation_rule_settings = array(
|
|
170 |
'allowed_image_types' =>
|
171 |
array(
|
172 |
'template' => 'inputCheckbox',
|
173 |
-
'label' => 'Allowed Images',
|
174 |
-
'help_text' => 'Allowed Images',
|
175 |
'fileTypes' =>
|
176 |
array(
|
177 |
array(
|
178 |
-
'title' => 'JPG',
|
179 |
'types' => array( 'jpg|jpeg', ),
|
180 |
),
|
181 |
array(
|
182 |
-
'title' => 'PNG',
|
183 |
'types' => array( 'png' ),
|
184 |
),
|
185 |
array(
|
186 |
-
'title' => 'GIF',
|
187 |
'types' => array( 'gif' ),
|
188 |
),
|
189 |
),
|
190 |
'options' => array(
|
191 |
array(
|
192 |
-
'label' => 'JPG',
|
193 |
'value' => 'jpg|jpeg',
|
194 |
),
|
195 |
array(
|
196 |
-
'label' => 'PNG',
|
197 |
'value' => 'png',
|
198 |
),
|
199 |
array(
|
200 |
-
'label' => 'GIF',
|
201 |
'value' => 'gif',
|
202 |
),
|
203 |
),
|
204 |
)
|
205 |
);
|
206 |
|
207 |
-
return apply_filters( 'fluent_editor_validation_rule_settings', $validation_rule_settings );
|
19 |
'required' =>
|
20 |
array(
|
21 |
'template' => 'inputRadio',
|
22 |
+
'label' => __('Required', 'fluentform'),
|
23 |
+
'help_text' => __('Select whether this field is a required field for the form or not.', 'fluentform'),
|
24 |
'options' =>
|
25 |
array(
|
26 |
array(
|
27 |
'value' => true,
|
28 |
+
'label' => __('Yes', 'fluentform'),
|
29 |
),
|
30 |
array(
|
31 |
'value' => false,
|
32 |
+
'label' => __('No', 'fluentform'),
|
33 |
),
|
34 |
),
|
35 |
),
|
37 |
array(
|
38 |
'template' => 'inputText',
|
39 |
'type' => 'number',
|
40 |
+
'label' => __('Min Value', 'fluentform'),
|
41 |
+
'help_text' => __('Minimum value for the input field.', 'fluentform'),
|
42 |
),
|
43 |
'max' =>
|
44 |
array(
|
45 |
'template' => 'inputText',
|
46 |
'type' => 'number',
|
47 |
+
'label' => __('Max Value', 'fluentform'),
|
48 |
+
'help_text' => __('Maximum value for the input field.', 'fluentform'),
|
49 |
),
|
50 |
'max_file_size' =>
|
51 |
array(
|
52 |
'template' => 'maxFileSize',
|
53 |
+
'label' => __('Max File Size', 'fluentform'),
|
54 |
+
'help_text' => __('The file size limit uploaded by the user.', 'fluentform'),
|
55 |
),
|
56 |
'max_file_count' =>
|
57 |
array(
|
58 |
'template' => 'inputText',
|
59 |
'type' => 'number',
|
60 |
+
'label' => __('Max Files Count', 'fluentform'),
|
61 |
+
'help_text' => __('Maximum user file upload number.', 'fluentform'),
|
62 |
),
|
63 |
'allowed_file_types' =>
|
64 |
array(
|
65 |
'template' => 'inputCheckbox',
|
66 |
+
'label' => __('Allowed Files', 'fluentform'),
|
67 |
+
'help_text' => __('Allowed Files', 'fluentform'),
|
68 |
'fileTypes' =>
|
69 |
array(
|
70 |
array(
|
71 |
+
'title' => __('Images', 'fluentform'),
|
72 |
'types' => array( 'jpg', 'jpeg', 'gif', 'png', 'bmp' ),
|
73 |
),
|
74 |
array(
|
75 |
+
'title' => __('Audio', 'fluentform'),
|
76 |
'types' => array( 'mp3', 'wav', 'ogg', 'wma', 'mka', 'm4a', 'ra', 'mid', 'midi' ),
|
77 |
),
|
78 |
array(
|
79 |
+
'title' => __('Video', 'fluentform'),
|
80 |
'types' => array(
|
81 |
'avi',
|
82 |
'divx',
|
93 |
),
|
94 |
),
|
95 |
array(
|
96 |
+
'title' => __('PDF', 'fluentform'),
|
97 |
'types' => array( 'pdf' ),
|
98 |
),
|
99 |
array(
|
100 |
+
'title' => __('Docs', 'fluentform'),
|
101 |
'types' => array(
|
102 |
'doc',
|
103 |
'ppt',
|
119 |
),
|
120 |
),
|
121 |
array(
|
122 |
+
'title' => __('Zip Archives', 'fluentform'),
|
123 |
'types' => array( 'zip', 'gz', 'gzip', 'rar', '7z', ),
|
124 |
),
|
125 |
array(
|
126 |
+
'title' => __('Executable Files', 'fluentform'),
|
127 |
'types' =>
|
128 |
array( 'exe' ),
|
129 |
),
|
130 |
array(
|
131 |
+
'title' => __('CSV', 'fluentform'),
|
132 |
'types' => array( 'csv' ),
|
133 |
),
|
134 |
),
|
135 |
'options' => array(
|
136 |
array(
|
137 |
+
'label' => __('Images (jpg, jpeg, gif, png, bmp)', 'fluentform'),
|
138 |
'value' => 'jpg|jpeg|gif|png|bmp',
|
139 |
),
|
140 |
array(
|
141 |
+
'label' => __('Audio (mp3, wav, ogg, wma, mka, m4a, ra, mid, midi)', 'fluentform'),
|
142 |
'value' => 'mp3|wav|ogg|wma|mka|m4a|ra|mid|midi',
|
143 |
),
|
144 |
array(
|
145 |
+
'label' => __('Video (avi, divx, flv, mov, ogv, mkv, mp4, m4v, divx, mpg, mpeg, mpe)', 'fluentform'),
|
146 |
'value' => 'avi|divx|flv|mov|ogv|mkv|mp4|m4v|divx|mpg|mpeg|mpe|video/quicktime|qt',
|
147 |
),
|
148 |
array(
|
149 |
+
'label' => __('PDF (pdf)', 'fluentform'),
|
150 |
'value' => 'pdf',
|
151 |
),
|
152 |
array(
|
153 |
+
'label' => __('Docs (doc, ppt, pps, xls, mdb, docx, xlsx, pptx, odt, odp, ods, odg, odc, odb, odf, rtf, txt)', 'fluentform'),
|
154 |
'value' => 'doc|ppt|pps|xls|mdb|docx|xlsx|pptx|odt|odp|ods|odg|odc|odb|odf|rtf|txt',
|
155 |
),
|
156 |
array(
|
157 |
+
'label' => __('Zip Archives (zip, gz, gzip, rar, 7z)', 'fluentform'),
|
158 |
'value' => 'zip|gz|gzip|rar|7z',
|
159 |
),
|
160 |
array(
|
161 |
+
'label' => __('Executable Files (exe)', 'fluentform'),
|
162 |
'value' => 'exe',
|
163 |
),
|
164 |
array(
|
165 |
+
'label' => __('CSV (csv)', 'fluentform'),
|
166 |
'value' => 'csv',
|
167 |
),
|
168 |
),
|
170 |
'allowed_image_types' =>
|
171 |
array(
|
172 |
'template' => 'inputCheckbox',
|
173 |
+
'label' => __('Allowed Images', 'fluentform'),
|
174 |
+
'help_text' => __('Allowed Images', 'fluentform'),
|
175 |
'fileTypes' =>
|
176 |
array(
|
177 |
array(
|
178 |
+
'title' => __('JPG', 'fluentform'),
|
179 |
'types' => array( 'jpg|jpeg', ),
|
180 |
),
|
181 |
array(
|
182 |
+
'title' => __('PNG', 'fluentform'),
|
183 |
'types' => array( 'png' ),
|
184 |
),
|
185 |
array(
|
186 |
+
'title' => __('GIF', 'fluentform'),
|
187 |
'types' => array( 'gif' ),
|
188 |
),
|
189 |
),
|
190 |
'options' => array(
|
191 |
array(
|
192 |
+
'label' => __('JPG', 'fluentform'),
|
193 |
'value' => 'jpg|jpeg',
|
194 |
),
|
195 |
array(
|
196 |
+
'label' => __('PNG', 'fluentform'),
|
197 |
'value' => 'png',
|
198 |
),
|
199 |
array(
|
200 |
+
'label' => __('GIF', 'fluentform'),
|
201 |
'value' => 'gif',
|
202 |
),
|
203 |
),
|
204 |
)
|
205 |
);
|
206 |
|
207 |
+
return apply_filters( 'fluent_editor_validation_rule_settings', $validation_rule_settings );
|
app/Services/Integrations/MailChimp.php
DELETED
@@ -1,441 +0,0 @@
|
|
1 |
-
<?php namespace FluentForm\App\Services\Integrations;
|
2 |
-
|
3 |
-
/**
|
4 |
-
* Super-simple, minimum abstraction MailChimp API v3 wrapper
|
5 |
-
* MailChimp API v3: http://developer.mailchimp.com
|
6 |
-
* This wrapper: https://github.com/drewm/mailchimp-api
|
7 |
-
*
|
8 |
-
* @author Drew McLellan <drew.mclellan@gmail.com>
|
9 |
-
* @version 2.4
|
10 |
-
*/
|
11 |
-
class MailChimp
|
12 |
-
{
|
13 |
-
private $api_key;
|
14 |
-
private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
|
15 |
-
|
16 |
-
const TIMEOUT = 10;
|
17 |
-
|
18 |
-
/* SSL Verification
|
19 |
-
Read before disabling:
|
20 |
-
http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
|
21 |
-
*/
|
22 |
-
public $verify_ssl = true;
|
23 |
-
|
24 |
-
private $request_successful = false;
|
25 |
-
private $last_error = '';
|
26 |
-
private $last_response = array();
|
27 |
-
private $last_request = array();
|
28 |
-
|
29 |
-
/**
|
30 |
-
* Create a new instance
|
31 |
-
* @param string $api_key Your MailChimp API key
|
32 |
-
* @param string $api_endpoint Optional custom API endpoint
|
33 |
-
* @throws \Exception
|
34 |
-
*/
|
35 |
-
public function __construct($api_key, $api_endpoint = null)
|
36 |
-
{
|
37 |
-
$this->api_key = $api_key;
|
38 |
-
|
39 |
-
if ($api_endpoint === null) {
|
40 |
-
if (strpos($this->api_key, '-') === false) {
|
41 |
-
throw new \Exception("Invalid MailChimp API key `{$api_key}` supplied.");
|
42 |
-
}
|
43 |
-
list(, $data_center) = explode('-', $this->api_key);
|
44 |
-
$this->api_endpoint = str_replace('<dc>', $data_center, $this->api_endpoint);
|
45 |
-
} else {
|
46 |
-
$this->api_endpoint = $api_endpoint;
|
47 |
-
}
|
48 |
-
|
49 |
-
$this->last_response = array('headers' => null, 'body' => null);
|
50 |
-
}
|
51 |
-
|
52 |
-
/**
|
53 |
-
* @return string The url to the API endpoint
|
54 |
-
*/
|
55 |
-
public function getApiEndpoint()
|
56 |
-
{
|
57 |
-
return $this->api_endpoint;
|
58 |
-
}
|
59 |
-
|
60 |
-
|
61 |
-
/**
|
62 |
-
* Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
|
63 |
-
* @param string $email The subscriber's email address
|
64 |
-
* @return string Hashed version of the input
|
65 |
-
*/
|
66 |
-
public function subscriberHash($email)
|
67 |
-
{
|
68 |
-
return md5(strtolower($email));
|
69 |
-
}
|
70 |
-
|
71 |
-
/**
|
72 |
-
* Was the last request successful?
|
73 |
-
* @return bool True for success, false for failure
|
74 |
-
*/
|
75 |
-
public function success()
|
76 |
-
{
|
77 |
-
return $this->request_successful;
|
78 |
-
}
|
79 |
-
|
80 |
-
/**
|
81 |
-
* Get the last error returned by either the network transport, or by the API.
|
82 |
-
* If something didn't work, this should contain the string describing the problem.
|
83 |
-
* @return string|false describing the error
|
84 |
-
*/
|
85 |
-
public function getLastError()
|
86 |
-
{
|
87 |
-
return $this->last_error ?: false;
|
88 |
-
}
|
89 |
-
|
90 |
-
/**
|
91 |
-
* Get an array containing the HTTP headers and the body of the API response.
|
92 |
-
* @return array Assoc array with keys 'headers' and 'body'
|
93 |
-
*/
|
94 |
-
public function getLastResponse()
|
95 |
-
{
|
96 |
-
return $this->last_response;
|
97 |
-
}
|
98 |
-
|
99 |
-
/**
|
100 |
-
* Get an array containing the HTTP headers and the body of the API request.
|
101 |
-
* @return array Assoc array
|
102 |
-
*/
|
103 |
-
public function getLastRequest()
|
104 |
-
{
|
105 |
-
return $this->last_request;
|
106 |
-
}
|
107 |
-
|
108 |
-
/**
|
109 |
-
* Make an HTTP DELETE request - for deleting data
|
110 |
-
* @param string $method URL of the API request method
|
111 |
-
* @param array $args Assoc array of arguments (if any)
|
112 |
-
* @param int $timeout Timeout limit for request in seconds
|
113 |
-
* @return array|false Assoc array of API response, decoded from JSON
|
114 |
-
*/
|
115 |
-
public function delete($method, $args = array(), $timeout = self::TIMEOUT)
|
116 |
-
{
|
117 |
-
return $this->makeRequest('delete', $method, $args, $timeout);
|
118 |
-
}
|
119 |
-
|
120 |
-
/**
|
121 |
-
* Make an HTTP GET request - for retrieving data
|
122 |
-
* @param string $method URL of the API request method
|
123 |
-
* @param array $args Assoc array of arguments (usually your data)
|
124 |
-
* @param int $timeout Timeout limit for request in seconds
|
125 |
-
* @return array|false Assoc array of API response, decoded from JSON
|
126 |
-
*/
|
127 |
-
public function get($method, $args = array(), $timeout = self::TIMEOUT)
|
128 |
-
{
|
129 |
-
return $this->makeRequest('get', $method, $args, $timeout);
|
130 |
-
}
|
131 |
-
|
132 |
-
/**
|
133 |
-
* Make an HTTP PATCH request - for performing partial updates
|
134 |
-
* @param string $method URL of the API request method
|
135 |
-
* @param array $args Assoc array of arguments (usually your data)
|
136 |
-
* @param int $timeout Timeout limit for request in seconds
|
137 |
-
* @return array|false Assoc array of API response, decoded from JSON
|
138 |
-
*/
|
139 |
-
public function patch($method, $args = array(), $timeout = self::TIMEOUT)
|
140 |
-
{
|
141 |
-
return $this->makeRequest('patch', $method, $args, $timeout);
|
142 |
-
}
|
143 |
-
|
144 |
-
/**
|
145 |
-
* Make an HTTP POST request - for creating and updating items
|
146 |
-
* @param string $method URL of the API request method
|
147 |
-
* @param array $args Assoc array of arguments (usually your data)
|
148 |
-
* @param int $timeout Timeout limit for request in seconds
|
149 |
-
* @return array|false Assoc array of API response, decoded from JSON
|
150 |
-
*/
|
151 |
-
public function post($method, $args = array(), $timeout = self::TIMEOUT)
|
152 |
-
{
|
153 |
-
return $this->makeRequest('post', $method, $args, $timeout);
|
154 |
-
}
|
155 |
-
|
156 |
-
/**
|
157 |
-
* Make an HTTP PUT request - for creating new items
|
158 |
-
* @param string $method URL of the API request method
|
159 |
-
* @param array $args Assoc array of arguments (usually your data)
|
160 |
-
* @param int $timeout Timeout limit for request in seconds
|
161 |
-
* @return array|false Assoc array of API response, decoded from JSON
|
162 |
-
*/
|
163 |
-
public function put($method, $args = array(), $timeout = self::TIMEOUT)
|
164 |
-
{
|
165 |
-
return $this->makeRequest('put', $method, $args, $timeout);
|
166 |
-
}
|
167 |
-
|
168 |
-
/**
|
169 |
-
* Performs the underlying HTTP request. Not very exciting.
|
170 |
-
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete
|
171 |
-
* @param string $method The API method to be called
|
172 |
-
* @param array $args Assoc array of parameters to be passed
|
173 |
-
* @param int $timeout
|
174 |
-
* @return array|false Assoc array of decoded result
|
175 |
-
* @throws \Exception
|
176 |
-
*/
|
177 |
-
private function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
|
178 |
-
{
|
179 |
-
if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
|
180 |
-
throw new \Exception("cURL support is required, but can't be found.");
|
181 |
-
}
|
182 |
-
|
183 |
-
$url = $this->api_endpoint . '/' . $method;
|
184 |
-
|
185 |
-
$response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
|
186 |
-
|
187 |
-
$httpHeader = array(
|
188 |
-
'Accept: application/vnd.api+json',
|
189 |
-
'Content-Type: application/vnd.api+json',
|
190 |
-
'Authorization: apikey ' . $this->api_key
|
191 |
-
);
|
192 |
-
|
193 |
-
if (isset($args["language"])) {
|
194 |
-
$httpHeader[] = "Accept-Language: " . $args["language"];
|
195 |
-
}
|
196 |
-
|
197 |
-
$ch = curl_init();
|
198 |
-
curl_setopt($ch, CURLOPT_URL, $url);
|
199 |
-
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
|
200 |
-
curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
|
201 |
-
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
202 |
-
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
203 |
-
curl_setopt($ch, CURLOPT_HEADER, true);
|
204 |
-
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
205 |
-
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
|
206 |
-
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
|
207 |
-
curl_setopt($ch, CURLOPT_ENCODING, '');
|
208 |
-
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
|
209 |
-
|
210 |
-
switch ($http_verb) {
|
211 |
-
case 'post':
|
212 |
-
curl_setopt($ch, CURLOPT_POST, true);
|
213 |
-
$this->attachRequestPayload($ch, $args);
|
214 |
-
break;
|
215 |
-
|
216 |
-
case 'get':
|
217 |
-
$query = http_build_query($args, '', '&');
|
218 |
-
curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
|
219 |
-
break;
|
220 |
-
|
221 |
-
case 'delete':
|
222 |
-
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
223 |
-
break;
|
224 |
-
|
225 |
-
case 'patch':
|
226 |
-
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
|
227 |
-
$this->attachRequestPayload($ch, $args);
|
228 |
-
break;
|
229 |
-
|
230 |
-
case 'put':
|
231 |
-
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
232 |
-
$this->attachRequestPayload($ch, $args);
|
233 |
-
break;
|
234 |
-
}
|
235 |
-
|
236 |
-
$responseContent = curl_exec($ch);
|
237 |
-
$response['headers'] = curl_getinfo($ch);
|
238 |
-
$response = $this->setResponseState($response, $responseContent, $ch);
|
239 |
-
$formattedResponse = $this->formatResponse($response);
|
240 |
-
|
241 |
-
curl_close($ch);
|
242 |
-
|
243 |
-
$this->determineSuccess($response, $formattedResponse, $timeout);
|
244 |
-
|
245 |
-
return $formattedResponse;
|
246 |
-
}
|
247 |
-
|
248 |
-
/**
|
249 |
-
* @param string $http_verb
|
250 |
-
* @param string $method
|
251 |
-
* @param string $url
|
252 |
-
* @param integer $timeout
|
253 |
-
*/
|
254 |
-
private function prepareStateForRequest($http_verb, $method, $url, $timeout)
|
255 |
-
{
|
256 |
-
$this->last_error = '';
|
257 |
-
|
258 |
-
$this->request_successful = false;
|
259 |
-
|
260 |
-
$this->last_response = array(
|
261 |
-
'headers' => null, // array of details from curl_getinfo()
|
262 |
-
'httpHeaders' => null, // array of HTTP headers
|
263 |
-
'body' => null // content of the response
|
264 |
-
);
|
265 |
-
|
266 |
-
$this->last_request = array(
|
267 |
-
'method' => $http_verb,
|
268 |
-
'path' => $method,
|
269 |
-
'url' => $url,
|
270 |
-
'body' => '',
|
271 |
-
'timeout' => $timeout,
|
272 |
-
);
|
273 |
-
|
274 |
-
return $this->last_response;
|
275 |
-
}
|
276 |
-
|
277 |
-
/**
|
278 |
-
* Get the HTTP headers as an array of header-name => header-value pairs.
|
279 |
-
*
|
280 |
-
* The "Link" header is parsed into an associative array based on the
|
281 |
-
* rel names it contains. The original value is available under
|
282 |
-
* the "_raw" key.
|
283 |
-
*
|
284 |
-
* @param string $headersAsString
|
285 |
-
* @return array
|
286 |
-
*/
|
287 |
-
private function getHeadersAsArray($headersAsString)
|
288 |
-
{
|
289 |
-
$headers = array();
|
290 |
-
|
291 |
-
foreach (explode("\r\n", $headersAsString) as $i => $line) {
|
292 |
-
if ($i === 0) { // HTTP code
|
293 |
-
continue;
|
294 |
-
}
|
295 |
-
|
296 |
-
$line = trim($line);
|
297 |
-
if (empty($line)) {
|
298 |
-
continue;
|
299 |
-
}
|
300 |
-
|
301 |
-
list($key, $value) = explode(': ', $line);
|
302 |
-
|
303 |
-
if ($key == 'Link') {
|
304 |
-
$value = array_merge(
|
305 |
-
array('_raw' => $value),
|
306 |
-
$this->getLinkHeaderAsArray($value)
|
307 |
-
);
|
308 |
-
}
|
309 |
-
|
310 |
-
$headers[$key] = $value;
|
311 |
-
}
|
312 |
-
|
313 |
-
return $headers;
|
314 |
-
}
|
315 |
-
|
316 |
-
/**
|
317 |
-
* Extract all rel => URL pairs from the provided Link header value
|
318 |
-
*
|
319 |
-
* Mailchimp only implements the URI reference and relation type from
|
320 |
-
* RFC 5988, so the value of the header is something like this:
|
321 |
-
*
|
322 |
-
* 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy", <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
|
323 |
-
*
|
324 |
-
* @param string $linkHeaderAsString
|
325 |
-
* @return array
|
326 |
-
*/
|
327 |
-
private function getLinkHeaderAsArray($linkHeaderAsString)
|
328 |
-
{
|
329 |
-
$urls = array();
|
330 |
-
|
331 |
-
if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
|
332 |
-
foreach ($matches[2] as $i => $relName) {
|
333 |
-
$urls[$relName] = $matches[1][$i];
|
334 |
-
}
|
335 |
-
}
|
336 |
-
|
337 |
-
return $urls;
|
338 |
-
}
|
339 |
-
|
340 |
-
/**
|
341 |
-
* Encode the data and attach it to the request
|
342 |
-
* @param resource $ch cURL session handle, used by reference
|
343 |
-
* @param array $data Assoc array of data to attach
|
344 |
-
*/
|
345 |
-
private function attachRequestPayload(&$ch, $data)
|
346 |
-
{
|
347 |
-
$encoded = json_encode($data);
|
348 |
-
$this->last_request['body'] = $encoded;
|
349 |
-
curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
|
350 |
-
}
|
351 |
-
|
352 |
-
/**
|
353 |
-
* Decode the response and format any error messages for debugging
|
354 |
-
* @param array $response The response from the curl request
|
355 |
-
* @return array|false The JSON decoded into an array
|
356 |
-
*/
|
357 |
-
private function formatResponse($response)
|
358 |
-
{
|
359 |
-
$this->last_response = $response;
|
360 |
-
|
361 |
-
if (!empty($response['body'])) {
|
362 |
-
return json_decode($response['body'], true);
|
363 |
-
}
|
364 |
-
|
365 |
-
return false;
|
366 |
-
}
|
367 |
-
|
368 |
-
/**
|
369 |
-
* Do post-request formatting and setting state from the response
|
370 |
-
* @param array $response The response from the curl request
|
371 |
-
* @param string $responseContent The body of the response from the curl request
|
372 |
-
* * @return array The modified response
|
373 |
-
*/
|
374 |
-
private function setResponseState($response, $responseContent, $ch)
|
375 |
-
{
|
376 |
-
if ($responseContent === false) {
|
377 |
-
$this->last_error = curl_error($ch);
|
378 |
-
} else {
|
379 |
-
|
380 |
-
$headerSize = $response['headers']['header_size'];
|
381 |
-
|
382 |
-
$response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
|
383 |
-
$response['body'] = substr($responseContent, $headerSize);
|
384 |
-
|
385 |
-
if (isset($response['headers']['request_header'])) {
|
386 |
-
$this->last_request['headers'] = $response['headers']['request_header'];
|
387 |
-
}
|
388 |
-
}
|
389 |
-
|
390 |
-
return $response;
|
391 |
-
}
|
392 |
-
|
393 |
-
/**
|
394 |
-
* Check if the response was successful or a failure. If it failed, store the error.
|
395 |
-
* @param array $response The response from the curl request
|
396 |
-
* @param array|false $formattedResponse The response body payload from the curl request
|
397 |
-
* @param int $timeout The timeout supplied to the curl request.
|
398 |
-
* @return bool If the request was successful
|
399 |
-
*/
|
400 |
-
private function determineSuccess($response, $formattedResponse, $timeout)
|
401 |
-
{
|
402 |
-
$status = $this->findHTTPStatus($response, $formattedResponse);
|
403 |
-
|
404 |
-
if ($status >= 200 && $status <= 299) {
|
405 |
-
$this->request_successful = true;
|
406 |
-
return true;
|
407 |
-
}
|
408 |
-
|
409 |
-
if (isset($formattedResponse['detail'])) {
|
410 |
-
$this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
|
411 |
-
return false;
|
412 |
-
}
|
413 |
-
|
414 |
-
if( $timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout ) {
|
415 |
-
$this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time'] );
|
416 |
-
return false;
|
417 |
-
}
|
418 |
-
|
419 |
-
$this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
|
420 |
-
return false;
|
421 |
-
}
|
422 |
-
|
423 |
-
/**
|
424 |
-
* Find the HTTP status code from the headers or API response body
|
425 |
-
* @param array $response The response from the curl request
|
426 |
-
* @param array|false $formattedResponse The response body payload from the curl request
|
427 |
-
* @return int HTTP status code
|
428 |
-
*/
|
429 |
-
private function findHTTPStatus($response, $formattedResponse)
|
430 |
-
{
|
431 |
-
if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
|
432 |
-
return (int) $response['headers']['http_code'];
|
433 |
-
}
|
434 |
-
|
435 |
-
if (!empty($response['body']) && isset($formattedResponse['status'])) {
|
436 |
-
return (int) $formattedResponse['status'];
|
437 |
-
}
|
438 |
-
|
439 |
-
return 418;
|
440 |
-
}
|
441 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/MailChimp/MailChimpAsyncSubscriber.php
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Services\Integrations\MailChimp;
|
4 |
-
|
5 |
-
use FluentForm\App\Services\WPAsync\WPAsyncRequest;
|
6 |
-
use FluentForm\App\Services\Integrations\MailChimp\MailChimpIntegration;
|
7 |
-
|
8 |
-
class MailChimpAsyncSubscriber extends WPAsyncRequest
|
9 |
-
{
|
10 |
-
protected $app = null;
|
11 |
-
|
12 |
-
/**
|
13 |
-
* @var string
|
14 |
-
*/
|
15 |
-
protected $action = 'fluentform_subscribe_mailchimp';
|
16 |
-
|
17 |
-
public function __construct($app)
|
18 |
-
{
|
19 |
-
$this->app = $app;
|
20 |
-
parent::__construct();
|
21 |
-
}
|
22 |
-
|
23 |
-
/**
|
24 |
-
* Handle
|
25 |
-
*
|
26 |
-
* Override this method to perform any actions required
|
27 |
-
* during the async request.
|
28 |
-
*/
|
29 |
-
public function handle()
|
30 |
-
{
|
31 |
-
$data = $_POST['form_data'];
|
32 |
-
$formId = intval($_POST['form_id']);
|
33 |
-
$entryId = intval($_POST['entry_id']);
|
34 |
-
$form = wpFluent()->table('fluentform_forms')->find($formId);
|
35 |
-
(new MailChimpIntegration($this->app))->subscribe($data, $form, $entryId);
|
36 |
-
}
|
37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/Slack/SlackAsyncNotifier.php
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Services\Integrations\Slack;
|
4 |
-
|
5 |
-
use FluentForm\App\Services\WPAsync\WPAsyncRequest;
|
6 |
-
use FluentForm\App\Services\Integrations\Slack\Slack;
|
7 |
-
|
8 |
-
class SlackAsyncNotifier extends WPAsyncRequest
|
9 |
-
{
|
10 |
-
protected $app = null;
|
11 |
-
|
12 |
-
/**
|
13 |
-
* @var string
|
14 |
-
*/
|
15 |
-
protected $action = 'fluentform_subscribe_slack';
|
16 |
-
|
17 |
-
public function __construct($app)
|
18 |
-
{
|
19 |
-
$this->app = $app;
|
20 |
-
parent::__construct();
|
21 |
-
}
|
22 |
-
|
23 |
-
/**
|
24 |
-
* Handle
|
25 |
-
*
|
26 |
-
* Override this method to perform any actions required
|
27 |
-
* during the async request.
|
28 |
-
*/
|
29 |
-
public function handle()
|
30 |
-
{
|
31 |
-
$data = $_POST['form_data'];
|
32 |
-
$formId = intval($_POST['form_id']);
|
33 |
-
$entryId = intval($_POST['entry_id']);
|
34 |
-
$form = wpFluent()->table('fluentform_forms')->find($formId);
|
35 |
-
Slack::notify($data, $form, $entryId);
|
36 |
-
}
|
37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/.gitignore
DELETED
@@ -1,4 +0,0 @@
|
|
1 |
-
vendor/
|
2 |
-
.idea/
|
3 |
-
composer.lock
|
4 |
-
composer.phar
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/LICENSE
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
The MIT License (MIT)
|
2 |
-
|
3 |
-
Copyright (c) 2015 ActiveCampaign
|
4 |
-
|
5 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
-
of this software and associated documentation files (the "Software"), to deal
|
7 |
-
in the Software without restriction, including without limitation the rights
|
8 |
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
-
copies of the Software, and to permit persons to whom the Software is
|
10 |
-
furnished to do so, subject to the following conditions:
|
11 |
-
|
12 |
-
The above copyright notice and this permission notice shall be included in all
|
13 |
-
copies or substantial portions of the Software.
|
14 |
-
|
15 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
-
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/README.md
DELETED
@@ -1,83 +0,0 @@
|
|
1 |
-
# ActiveCampaign PHP API Wrapper
|
2 |
-
|
3 |
-
This is the official PHP wrapper for the ActiveCampaign API. The purpose of these files is to provide a simple interface to the ActiveCampaign API. You are **not** required to use these files (in order to use the ActiveCampaign API), but it's recommended for a few reasons:
|
4 |
-
|
5 |
-
1. It's a lot easier to get set up and use (as opposed to coding everything from scratch on your own).
|
6 |
-
2. It's fully supported by ActiveCampaign, meaning we fix any issues immediately, as well as continually improve the wrapper as the software changes and evolves.
|
7 |
-
3. It's often the standard approach for demonstrating API requests when using ActiveCampaign support.
|
8 |
-
|
9 |
-
Both customers of our hosted platform and On-Site edition can use these files. On-Site customers should clone the source and switch to the <a href="https://github.com/ActiveCampaign/activecampaign-api-php/tree/onsite">"onsite" branch</a>, as that is geared towards the On-Site edition. Many features of the hosted platform are not available in the On-Site edition.
|
10 |
-
|
11 |
-
## Installation
|
12 |
-
|
13 |
-
You can install **activecampaign-api-php** by [downloading (.zip)](https://github.com/ActiveCampaign/activecampaign-api-php/zipball/master) or cloning the source:
|
14 |
-
|
15 |
-
`git clone git@github.com:ActiveCampaign/activecampaign-api-php.git`
|
16 |
-
|
17 |
-
### Composer
|
18 |
-
|
19 |
-
If you are using Composer, create your `composer.json` file ([example here](examples-composer/composer.json)).
|
20 |
-
|
21 |
-
Then load the `composer.phar` file in that directory:
|
22 |
-
|
23 |
-
`curl -sS https://getcomposer.org/installer | php`
|
24 |
-
|
25 |
-
Next, run install to load the ActiveCampaign library:
|
26 |
-
|
27 |
-
`php composer.phar install`
|
28 |
-
|
29 |
-
You should then see the `activecampaign` folder inside `vendor`.
|
30 |
-
|
31 |
-
[Read more about using Composer](https://getcomposer.org/doc/).
|
32 |
-
|
33 |
-
## Example Usage
|
34 |
-
|
35 |
-
### Composer
|
36 |
-
|
37 |
-
In your script just include the `autoload.php` file to load all classes:
|
38 |
-
|
39 |
-
`require "vendor/autoload.php";`
|
40 |
-
|
41 |
-
Next, create a class instance of `ActiveCampaign`:
|
42 |
-
|
43 |
-
`$ac = new ActiveCampaign("API_URL", "API_KEY");`
|
44 |
-
|
45 |
-
That's it!
|
46 |
-
|
47 |
-
### includes/config.php
|
48 |
-
|
49 |
-
define("ACTIVECAMPAIGN_URL", "API_URL");
|
50 |
-
define("ACTIVECAMPAIGN_API_KEY", "API_KEY");
|
51 |
-
|
52 |
-
### examples.php
|
53 |
-
|
54 |
-
require_once("includes/ActiveCampaign.class.php");
|
55 |
-
|
56 |
-
$ac = new ActiveCampaign(ACTIVECAMPAIGN_URL, ACTIVECAMPAIGN_API_KEY);
|
57 |
-
|
58 |
-
// Adjust the default cURL timeout
|
59 |
-
$ac->set_curl_timeout(10);
|
60 |
-
|
61 |
-
$account = $ac->api("account/view");
|
62 |
-
|
63 |
-
Or just include everything in the same PHP file:
|
64 |
-
|
65 |
-
define("ACTIVECAMPAIGN_URL", "API_URL");
|
66 |
-
define("ACTIVECAMPAIGN_API_KEY", "API_KEY");
|
67 |
-
require_once("includes/ActiveCampaign.class.php");
|
68 |
-
$ac = new ActiveCampaign(ACTIVECAMPAIGN_URL, ACTIVECAMPAIGN_API_KEY);
|
69 |
-
|
70 |
-
// Adjust the default cURL timeout
|
71 |
-
$ac->set_curl_timeout(10);
|
72 |
-
|
73 |
-
$account = $ac->api("account/view");
|
74 |
-
|
75 |
-
See our [examples file](examples.php) for more in-depth samples.
|
76 |
-
|
77 |
-
## Full Documentation
|
78 |
-
|
79 |
-
[Click here to view our full API documentation.](http://activecampaign.com/api)
|
80 |
-
|
81 |
-
## Reporting Issues
|
82 |
-
|
83 |
-
We'd love to help if you have questions or problems. Report issues using the [Github Issue Tracker](https://github.com/ActiveCampaign/activecampaign-api-php/issues) or email help@activecampaign.com.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/composer.json
DELETED
@@ -1,24 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "activecampaign/api-php",
|
3 |
-
"description": "Official PHP wrapper for the ActiveCampaign API.",
|
4 |
-
"keywords": ["activecampaign", "email-marketing", "newsletter", "marketing-automation", "subscribe", "forms", "emails", "automation"],
|
5 |
-
"homepage": "https://github.com/ActiveCampaign/activecampaign-api-php",
|
6 |
-
"time": "2017-04-26",
|
7 |
-
"type": "library",
|
8 |
-
"license": "MIT",
|
9 |
-
"support": {
|
10 |
-
"email": "help@activecampaign.com",
|
11 |
-
"issues": "https://github.com/ActiveCampaign/activecampaign-api-php/issues",
|
12 |
-
"forum": "http://feedback.activecampaign.com/forums/238014-feedback-ideas/category/78647-api"
|
13 |
-
},
|
14 |
-
"require": {
|
15 |
-
"php": ">=5.3.0",
|
16 |
-
"ext-curl": "*"
|
17 |
-
},
|
18 |
-
"autoload": {
|
19 |
-
"classmap": [
|
20 |
-
"includes/"
|
21 |
-
]
|
22 |
-
},
|
23 |
-
"minimum-stability": "stable"
|
24 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/examples-composer/composer.json
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "test/test",
|
3 |
-
"description": "Testing using the ActiveCampaign PHP API wrapper with Composer.",
|
4 |
-
"authors": [{
|
5 |
-
"name": "Matt Thommes",
|
6 |
-
"email": "matt@activecampaign.com",
|
7 |
-
"homepage": "http://www.activecampaign.com",
|
8 |
-
"role": "Developer"
|
9 |
-
}
|
10 |
-
],
|
11 |
-
"require": {
|
12 |
-
"activecampaign/api-php": "1.0.0"
|
13 |
-
},
|
14 |
-
"minimum-stability": "dev"
|
15 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/examples-composer/index.php
DELETED
@@ -1,149 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
require "vendor/autoload.php";
|
4 |
-
|
5 |
-
$ac = new ActiveCampaign("API_URL", "API_KEY");
|
6 |
-
|
7 |
-
if (!(int)$ac->credentials_test()) {
|
8 |
-
echo "<p>Access denied: Invalid credentials (URL and/or API key).</p>";
|
9 |
-
exit();
|
10 |
-
}
|
11 |
-
|
12 |
-
echo "<p>Credentials valid! Proceeding...</p>";
|
13 |
-
|
14 |
-
/*
|
15 |
-
* VIEW ACCOUNT DETAILS.
|
16 |
-
*/
|
17 |
-
|
18 |
-
$account = $ac->api("account/view");
|
19 |
-
|
20 |
-
echo "<pre>";
|
21 |
-
print_r($account);
|
22 |
-
echo "</pre>";
|
23 |
-
|
24 |
-
/*
|
25 |
-
* ADD NEW LIST.
|
26 |
-
*/
|
27 |
-
|
28 |
-
$list = array(
|
29 |
-
"name" => "List 3",
|
30 |
-
"sender_name" => "My Company",
|
31 |
-
"sender_addr1" => "123 S. Street",
|
32 |
-
"sender_city" => "Chicago",
|
33 |
-
"sender_zip" => "60601",
|
34 |
-
"sender_country" => "USA",
|
35 |
-
);
|
36 |
-
|
37 |
-
$list_add = $ac->api("list/add", $list);
|
38 |
-
|
39 |
-
if (!(int)$list_add->success) {
|
40 |
-
// request failed
|
41 |
-
echo "<p>Adding list failed. Error returned: " . $list_add->error . "</p>";
|
42 |
-
exit();
|
43 |
-
}
|
44 |
-
|
45 |
-
// successful request
|
46 |
-
$list_id = (int)$list_add->id;
|
47 |
-
echo "<p>List added successfully (ID {$list_id})!</p>";
|
48 |
-
|
49 |
-
/*
|
50 |
-
* ADD OR EDIT CONTACT (TO THE NEW LIST CREATED ABOVE).
|
51 |
-
*/
|
52 |
-
|
53 |
-
$contact = array(
|
54 |
-
"email" => "test@example.com",
|
55 |
-
"first_name" => "Test",
|
56 |
-
"last_name" => "Test",
|
57 |
-
"p[{$list_id}]" => $list_id,
|
58 |
-
"status[{$list_id}]" => 1, // "Active" status
|
59 |
-
);
|
60 |
-
|
61 |
-
$contact_sync = $ac->api("contact/sync", $contact);
|
62 |
-
|
63 |
-
if (!(int)$contact_sync->success) {
|
64 |
-
// request failed
|
65 |
-
echo "<p>Syncing contact failed. Error returned: " . $contact_sync->error . "</p>";
|
66 |
-
exit();
|
67 |
-
}
|
68 |
-
|
69 |
-
// successful request
|
70 |
-
$contact_id = (int)$contact_sync->subscriber_id;
|
71 |
-
echo "<p>Contact synced successfully (ID {$contact_id})!</p>";
|
72 |
-
|
73 |
-
/*
|
74 |
-
* VIEW ALL CONTACTS IN A LIST (RETURNS ID AND EMAIL).
|
75 |
-
*/
|
76 |
-
|
77 |
-
$ac->version(2);
|
78 |
-
$contacts_view = $ac->api("contact/list?listid=14&limit=500");
|
79 |
-
|
80 |
-
$ac->version(1);
|
81 |
-
|
82 |
-
/*
|
83 |
-
* ADD NEW EMAIL MESSAGE (FOR A CAMPAIGN).
|
84 |
-
*/
|
85 |
-
|
86 |
-
$message = array(
|
87 |
-
"format" => "mime",
|
88 |
-
"subject" => "Check out our latest deals!",
|
89 |
-
"fromemail" => "newsletter@test.com",
|
90 |
-
"fromname" => "Test from API",
|
91 |
-
"html" => "<p>My email newsletter.</p>",
|
92 |
-
"p[{$list_id}]" => $list_id,
|
93 |
-
);
|
94 |
-
|
95 |
-
$message_add = $ac->api("message/add", $message);
|
96 |
-
|
97 |
-
if (!(int)$message_add->success) {
|
98 |
-
// request failed
|
99 |
-
echo "<p>Adding email message failed. Error returned: " . $message_add->error . "</p>";
|
100 |
-
exit();
|
101 |
-
}
|
102 |
-
|
103 |
-
// successful request
|
104 |
-
$message_id = (int)$message_add->id;
|
105 |
-
echo "<p>Message added successfully (ID {$message_id})!</p>";
|
106 |
-
|
107 |
-
/*
|
108 |
-
* CREATE NEW CAMPAIGN (USING THE EMAIL MESSAGE CREATED ABOVE).
|
109 |
-
*/
|
110 |
-
|
111 |
-
$campaign = array(
|
112 |
-
"type" => "single",
|
113 |
-
"name" => "July Campaign", // internal name (message subject above is what contacts see)
|
114 |
-
"sdate" => "2013-07-01 00:00:00",
|
115 |
-
"status" => 1,
|
116 |
-
"public" => 1,
|
117 |
-
"tracklinks" => "all",
|
118 |
-
"trackreads" => 1,
|
119 |
-
"htmlunsub" => 1,
|
120 |
-
"p[{$list_id}]" => $list_id,
|
121 |
-
"m[{$message_id}]" => 100, // 100 percent of subscribers
|
122 |
-
);
|
123 |
-
|
124 |
-
$campaign_create = $ac->api("campaign/create", $campaign);
|
125 |
-
|
126 |
-
if (!(int)$campaign_create->success) {
|
127 |
-
// request failed
|
128 |
-
echo "<p>Creating campaign failed. Error returned: " . $campaign_create->error . "</p>";
|
129 |
-
exit();
|
130 |
-
}
|
131 |
-
|
132 |
-
// successful request
|
133 |
-
$campaign_id = (int)$campaign_create->id;
|
134 |
-
echo "<p>Campaign created and sent! (ID {$campaign_id})!</p>";
|
135 |
-
|
136 |
-
/*
|
137 |
-
* VIEW CAMPAIGN REPORTS (FOR THE CAMPAIGN CREATED ABOVE).
|
138 |
-
*/
|
139 |
-
|
140 |
-
$campaign_report_totals = $ac->api("campaign/report/totals?campaignid={$campaign_id}");
|
141 |
-
|
142 |
-
echo "<p>Reports:</p>";
|
143 |
-
echo "<pre>";
|
144 |
-
print_r($campaign_report_totals);
|
145 |
-
echo "</pre>";
|
146 |
-
|
147 |
-
?>
|
148 |
-
|
149 |
-
<a href="http://www.activecampaign.com/api">View more API examples!</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/examples.php
DELETED
@@ -1,165 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
require_once("includes/ActiveCampaign.class.php");
|
4 |
-
|
5 |
-
$ac = new ActiveCampaign("API_URL", "API_KEY");
|
6 |
-
|
7 |
-
/*
|
8 |
-
* TEST API CREDENTIALS.
|
9 |
-
*/
|
10 |
-
|
11 |
-
if (!(int)$ac->credentials_test()) {
|
12 |
-
echo "<p>Access denied: Invalid credentials (URL and/or API key).</p>";
|
13 |
-
exit();
|
14 |
-
}
|
15 |
-
|
16 |
-
echo "<p>Credentials valid! Proceeding...</p>";
|
17 |
-
|
18 |
-
/*
|
19 |
-
* VIEW ACCOUNT DETAILS.
|
20 |
-
*/
|
21 |
-
|
22 |
-
$account = $ac->api("account/view");
|
23 |
-
|
24 |
-
echo "<pre>";
|
25 |
-
print_r($account);
|
26 |
-
echo "</pre>";
|
27 |
-
|
28 |
-
/*
|
29 |
-
* ADD NEW LIST.
|
30 |
-
*/
|
31 |
-
|
32 |
-
$list = array(
|
33 |
-
"name" => "List 3",
|
34 |
-
"sender_name" => "My Company",
|
35 |
-
"sender_addr1" => "123 S. Street",
|
36 |
-
"sender_city" => "Chicago",
|
37 |
-
"sender_zip" => "60601",
|
38 |
-
"sender_country" => "USA",
|
39 |
-
);
|
40 |
-
|
41 |
-
$list_add = $ac->api("list/add", $list);
|
42 |
-
|
43 |
-
if (!(int)$list_add->success) {
|
44 |
-
// request failed
|
45 |
-
echo "<p>Adding list failed. Error returned: " . $list_add->error . "</p>";
|
46 |
-
exit();
|
47 |
-
}
|
48 |
-
|
49 |
-
// successful request
|
50 |
-
$list_id = (int)$list_add->id;
|
51 |
-
echo "<p>List added successfully (ID {$list_id})!</p>";
|
52 |
-
|
53 |
-
/*
|
54 |
-
* ADD OR EDIT CONTACT (TO THE NEW LIST CREATED ABOVE).
|
55 |
-
*/
|
56 |
-
|
57 |
-
$contact = array(
|
58 |
-
"email" => "test@example.com",
|
59 |
-
"first_name" => "Test",
|
60 |
-
"last_name" => "Test",
|
61 |
-
"p[{$list_id}]" => $list_id,
|
62 |
-
"status[{$list_id}]" => 1, // "Active" status
|
63 |
-
);
|
64 |
-
|
65 |
-
$contact_sync = $ac->api("contact/sync", $contact);
|
66 |
-
|
67 |
-
if (!(int)$contact_sync->success) {
|
68 |
-
// request failed
|
69 |
-
echo "<p>Syncing contact failed. Error returned: " . $contact_sync->error . "</p>";
|
70 |
-
exit();
|
71 |
-
}
|
72 |
-
|
73 |
-
// successful request
|
74 |
-
$contact_id = (int)$contact_sync->subscriber_id;
|
75 |
-
echo "<p>Contact synced successfully (ID {$contact_id})!</p>";
|
76 |
-
|
77 |
-
/*
|
78 |
-
* VIEW ALL CONTACTS IN A LIST (RETURNS ID AND EMAIL).
|
79 |
-
*/
|
80 |
-
|
81 |
-
$ac->version(2);
|
82 |
-
$contacts_view = $ac->api("contact/list?listid=14&limit=500");
|
83 |
-
|
84 |
-
$ac->version(1);
|
85 |
-
|
86 |
-
/*
|
87 |
-
* ADD NEW EMAIL MESSAGE (FOR A CAMPAIGN).
|
88 |
-
*/
|
89 |
-
|
90 |
-
$message = array(
|
91 |
-
"format" => "mime",
|
92 |
-
"subject" => "Check out our latest deals!",
|
93 |
-
"fromemail" => "newsletter@test.com",
|
94 |
-
"fromname" => "Test from API",
|
95 |
-
"html" => "<p>My email newsletter.</p>",
|
96 |
-
"p[{$list_id}]" => $list_id,
|
97 |
-
);
|
98 |
-
|
99 |
-
$message_add = $ac->api("message/add", $message);
|
100 |
-
|
101 |
-
if (!(int)$message_add->success) {
|
102 |
-
// request failed
|
103 |
-
echo "<p>Adding email message failed. Error returned: " . $message_add->error . "</p>";
|
104 |
-
exit();
|
105 |
-
}
|
106 |
-
|
107 |
-
// successful request
|
108 |
-
$message_id = (int)$message_add->id;
|
109 |
-
echo "<p>Message added successfully (ID {$message_id})!</p>";
|
110 |
-
|
111 |
-
/*
|
112 |
-
* CREATE NEW CAMPAIGN (USING THE EMAIL MESSAGE CREATED ABOVE).
|
113 |
-
*/
|
114 |
-
|
115 |
-
$campaign = array(
|
116 |
-
"type" => "single",
|
117 |
-
"name" => "July Campaign", // internal name (message subject above is what contacts see)
|
118 |
-
"sdate" => "2013-07-01 00:00:00",
|
119 |
-
"status" => 1,
|
120 |
-
"public" => 1,
|
121 |
-
"tracklinks" => "all",
|
122 |
-
"trackreads" => 1,
|
123 |
-
"htmlunsub" => 1,
|
124 |
-
"p[{$list_id}]" => $list_id,
|
125 |
-
"m[{$message_id}]" => 100, // 100 percent of subscribers
|
126 |
-
);
|
127 |
-
|
128 |
-
$campaign_create = $ac->api("campaign/create", $campaign);
|
129 |
-
|
130 |
-
if (!(int)$campaign_create->success) {
|
131 |
-
// request failed
|
132 |
-
echo "<p>Creating campaign failed. Error returned: " . $campaign_create->error . "</p>";
|
133 |
-
exit();
|
134 |
-
}
|
135 |
-
|
136 |
-
// successful request
|
137 |
-
$campaign_id = (int)$campaign_create->id;
|
138 |
-
echo "<p>Campaign created and sent! (ID {$campaign_id})!</p>";
|
139 |
-
|
140 |
-
/*
|
141 |
-
* VIEW CAMPAIGN REPORTS (FOR THE CAMPAIGN CREATED ABOVE).
|
142 |
-
*/
|
143 |
-
|
144 |
-
$campaign_report_totals = $ac->api("campaign/report/totals?campaignid={$campaign_id}");
|
145 |
-
|
146 |
-
echo "<p>Reports:</p>";
|
147 |
-
echo "<pre>";
|
148 |
-
print_r($campaign_report_totals);
|
149 |
-
echo "</pre>";
|
150 |
-
|
151 |
-
?>
|
152 |
-
|
153 |
-
<p><b>Note</b>: It can also be helpful to check our <a href="http://www.activecampaign.com/api/overview.php">API documentation</a> for the HTTP method that should be used for a particular endpoint as it can affect the format of your request.</p>
|
154 |
-
<p>Example: <pre>list_field_view</pre> GET</p>
|
155 |
-
<pre>
|
156 |
-
$ac->api("list/field/view?ids=all");
|
157 |
-
<pre>
|
158 |
-
<p>Query params appended for a GET request.</p>
|
159 |
-
|
160 |
-
<p>Example: <pre>list_field_edit</pre> POST</p>
|
161 |
-
<pre>
|
162 |
-
$ac->api("list/field/edit", array(/*POST params here.*/));
|
163 |
-
</pre>
|
164 |
-
|
165 |
-
<a href="http://www.activecampaign.com/api">View more API examples!</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Account.class.php
DELETED
@@ -1,73 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Account extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=account_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function cancel($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=account_cancel&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function edit($params, $post_data) {
|
30 |
-
$request_url = "{$this->url}&api_action=account_edit&api_output={$this->output}";
|
31 |
-
$response = $this->curl($request_url, $post_data);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function list_($params) {
|
36 |
-
$request_url = "{$this->url}&api_action=account_list&api_output={$this->output}&{$params}";
|
37 |
-
$response = $this->curl($request_url);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function name_check($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=account_name_check&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function plans($params) {
|
48 |
-
$request_url = "{$this->url}&api_action=account_plans&api_output={$this->output}&{$params}";
|
49 |
-
$response = $this->curl($request_url);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function status($params) {
|
54 |
-
$request_url = "{$this->url}&api_action=account_status&api_output={$this->output}&{$params}";
|
55 |
-
$response = $this->curl($request_url);
|
56 |
-
return $response;
|
57 |
-
}
|
58 |
-
|
59 |
-
function status_set($params) {
|
60 |
-
$request_url = "{$this->url}&api_action=account_status_set&api_output={$this->output}&{$params}";
|
61 |
-
$response = $this->curl($request_url);
|
62 |
-
return $response;
|
63 |
-
}
|
64 |
-
|
65 |
-
function view() {
|
66 |
-
$request_url = "{$this->url}&api_action=account_view&api_output={$this->output}";
|
67 |
-
$response = $this->curl($request_url);
|
68 |
-
return $response;
|
69 |
-
}
|
70 |
-
|
71 |
-
}
|
72 |
-
|
73 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/ActiveCampaign.class.php
DELETED
@@ -1,192 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
if ( !defined("ACTIVECAMPAIGN_URL") || (!defined("ACTIVECAMPAIGN_API_KEY") && !defined("ACTIVECAMPAIGN_API_USER") && !defined("ACTIVECAMPAIGN_API_PASS")) ) {
|
4 |
-
require_once(dirname(__FILE__) . "/config.php");
|
5 |
-
}
|
6 |
-
|
7 |
-
require_once("Connector.class.php");
|
8 |
-
|
9 |
-
/**
|
10 |
-
* Class ActiveCampaign
|
11 |
-
*/
|
12 |
-
class ActiveCampaign extends AC_Connector {
|
13 |
-
|
14 |
-
/**
|
15 |
-
* @var
|
16 |
-
*/
|
17 |
-
public $url_base;
|
18 |
-
|
19 |
-
/**
|
20 |
-
* @var
|
21 |
-
*/
|
22 |
-
public $url;
|
23 |
-
|
24 |
-
/**
|
25 |
-
* @var
|
26 |
-
*/
|
27 |
-
public $api_key;
|
28 |
-
|
29 |
-
/**
|
30 |
-
* @var
|
31 |
-
*/
|
32 |
-
public $track_email;
|
33 |
-
|
34 |
-
/**
|
35 |
-
* @var
|
36 |
-
*/
|
37 |
-
public $track_actid;
|
38 |
-
|
39 |
-
/**
|
40 |
-
* @var
|
41 |
-
*/
|
42 |
-
public $track_key;
|
43 |
-
|
44 |
-
/**
|
45 |
-
* @var int
|
46 |
-
*/
|
47 |
-
public $version = 1;
|
48 |
-
|
49 |
-
/**
|
50 |
-
* @var bool
|
51 |
-
*/
|
52 |
-
public $debug = false;
|
53 |
-
|
54 |
-
/**
|
55 |
-
* @var string
|
56 |
-
*/
|
57 |
-
public $curl_response_error = "";
|
58 |
-
|
59 |
-
/**
|
60 |
-
* ActiveCampaign constructor.
|
61 |
-
*
|
62 |
-
* @param $url
|
63 |
-
* @param $api_key
|
64 |
-
* @param string $api_user
|
65 |
-
* @param string $api_pass
|
66 |
-
*/
|
67 |
-
function __construct($url, $api_key, $api_user = "", $api_pass = "") {
|
68 |
-
$this->url_base = $this->url = $url;
|
69 |
-
$this->api_key = $api_key;
|
70 |
-
parent::__construct($url, $api_key, $api_user, $api_pass);
|
71 |
-
}
|
72 |
-
|
73 |
-
/**
|
74 |
-
* Set the version on the url
|
75 |
-
*
|
76 |
-
* @param $version
|
77 |
-
*/
|
78 |
-
function version($version) {
|
79 |
-
$this->version = (int)$version;
|
80 |
-
if ($version == 2) {
|
81 |
-
$this->url_base = $this->url_base . "/2";
|
82 |
-
}
|
83 |
-
}
|
84 |
-
|
85 |
-
/**
|
86 |
-
* Make api calls
|
87 |
-
*
|
88 |
-
* @param $path
|
89 |
-
* @param array $post_data
|
90 |
-
*
|
91 |
-
* @return mixed
|
92 |
-
*/
|
93 |
-
function api($path, $post_data = array()) {
|
94 |
-
// IE: "contact/view"
|
95 |
-
$components = explode("/", $path);
|
96 |
-
$component = $components[0];
|
97 |
-
|
98 |
-
if (count($components) > 2) {
|
99 |
-
// IE: "contact/tag/add?whatever"
|
100 |
-
// shift off the first item (the component, IE: "contact").
|
101 |
-
array_shift($components);
|
102 |
-
// IE: convert to "tag_add?whatever"
|
103 |
-
$method_str = implode("_", $components);
|
104 |
-
$components = array($component, $method_str);
|
105 |
-
}
|
106 |
-
|
107 |
-
if (preg_match("/\?/", $components[1])) {
|
108 |
-
// query params appended to method
|
109 |
-
// IE: contact/edit?overwrite=0
|
110 |
-
$method_arr = explode("?", $components[1]);
|
111 |
-
$method = $method_arr[0];
|
112 |
-
$params = $method_arr[1];
|
113 |
-
}
|
114 |
-
else {
|
115 |
-
// just a method provided
|
116 |
-
// IE: "contact/view
|
117 |
-
if ( isset($components[1]) ) {
|
118 |
-
$method = $components[1];
|
119 |
-
$params = "";
|
120 |
-
}
|
121 |
-
else {
|
122 |
-
return "Invalid method.";
|
123 |
-
}
|
124 |
-
}
|
125 |
-
|
126 |
-
// adjustments
|
127 |
-
if ($component == "list") {
|
128 |
-
// reserved word
|
129 |
-
$component = "list_";
|
130 |
-
}
|
131 |
-
elseif ($component == "branding") {
|
132 |
-
$component = "design";
|
133 |
-
}
|
134 |
-
elseif ($component == "sync") {
|
135 |
-
$component = "contact";
|
136 |
-
$method = "sync";
|
137 |
-
}
|
138 |
-
elseif ($component == "singlesignon") {
|
139 |
-
$component = "auth";
|
140 |
-
}
|
141 |
-
|
142 |
-
$class = ucwords($component); // IE: "contact" becomes "Contact"
|
143 |
-
$class = "AC_" . $class;
|
144 |
-
// IE: new Contact();
|
145 |
-
|
146 |
-
$add_tracking = false;
|
147 |
-
if ($class == "AC_Tracking") $add_tracking = true;
|
148 |
-
if ($class == "AC_Tags") {
|
149 |
-
$class = "AC_Tag";
|
150 |
-
}
|
151 |
-
|
152 |
-
$class = new $class($this->version, $this->url_base, $this->url, $this->api_key);
|
153 |
-
|
154 |
-
$class->set_curl_timeout($this->get_curl_timeout());
|
155 |
-
|
156 |
-
if ($add_tracking) {
|
157 |
-
$class->track_email = $this->track_email;
|
158 |
-
$class->track_actid = $this->track_actid;
|
159 |
-
$class->track_key = $this->track_key;
|
160 |
-
}
|
161 |
-
|
162 |
-
if ($method == "list") {
|
163 |
-
// reserved word
|
164 |
-
$method = "list_";
|
165 |
-
}
|
166 |
-
|
167 |
-
$class->debug = $this->debug;
|
168 |
-
|
169 |
-
$response = $class->$method($params, $post_data);
|
170 |
-
return $response;
|
171 |
-
}
|
172 |
-
}
|
173 |
-
|
174 |
-
require_once("Account.class.php");
|
175 |
-
require_once("Auth.class.php");
|
176 |
-
require_once("Automation.class.php");
|
177 |
-
require_once("Campaign.class.php");
|
178 |
-
require_once("Contact.class.php");
|
179 |
-
require_once("Deal.class.php");
|
180 |
-
require_once("Design.class.php");
|
181 |
-
require_once("Form.class.php");
|
182 |
-
require_once("Group.class.php");
|
183 |
-
require_once("List.class.php");
|
184 |
-
require_once("Message.class.php");
|
185 |
-
require_once("Organization.class.php");
|
186 |
-
require_once("Segment.class.php");
|
187 |
-
require_once("Settings.class.php");
|
188 |
-
require_once("Subscriber.class.php");
|
189 |
-
require_once("Tag.class.php");
|
190 |
-
require_once("Tracking.class.php");
|
191 |
-
require_once("User.class.php");
|
192 |
-
require_once("Webhook.class.php");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Auth.class.php
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Auth extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function singlesignon($params) {
|
18 |
-
$request_url = "{$this->url}&api_action=singlesignon&api_output={$this->output}&{$params}";
|
19 |
-
$response = $this->curl($request_url);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
}
|
24 |
-
|
25 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Automation.class.php
DELETED
@@ -1,51 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Automation extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function list_($params) {
|
18 |
-
$request_url = "{$this->url}&api_action=automation_list&api_output={$this->output}&{$params}";
|
19 |
-
$response = $this->curl($request_url);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function contact_add($params, $post_data) {
|
24 |
-
$request_url = "{$this->url}&api_action=automation_contact_add&api_output={$this->output}";
|
25 |
-
if ($params) $request_url .= "&{$params}";
|
26 |
-
$response = $this->curl($request_url, $post_data);
|
27 |
-
return $response;
|
28 |
-
}
|
29 |
-
|
30 |
-
function contact_remove($params, $post_data) {
|
31 |
-
$request_url = "{$this->url}&api_action=automation_contact_remove&api_output={$this->output}";
|
32 |
-
if ($params) $request_url .= "&{$params}";
|
33 |
-
$response = $this->curl($request_url, $post_data);
|
34 |
-
return $response;
|
35 |
-
}
|
36 |
-
|
37 |
-
function contact_list($params) {
|
38 |
-
$request_url = "{$this->url}&api_action=automation_contact_list&api_output={$this->output}&{$params}";
|
39 |
-
$response = $this->curl($request_url);
|
40 |
-
return $response;
|
41 |
-
}
|
42 |
-
|
43 |
-
function contact_view($params) {
|
44 |
-
$request_url = "{$this->url}&api_action=automation_contact_view&api_output={$this->output}&{$params}";
|
45 |
-
$response = $this->curl($request_url);
|
46 |
-
return $response;
|
47 |
-
}
|
48 |
-
|
49 |
-
}
|
50 |
-
|
51 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Campaign.class.php
DELETED
@@ -1,133 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Campaign extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function create($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=campaign_create&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function delete_list($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=campaign_delete_list&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete($params) {
|
30 |
-
$request_url = "{$this->url}&api_action=campaign_delete&api_output={$this->output}&{$params}";
|
31 |
-
$response = $this->curl($request_url);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function list_($params) {
|
36 |
-
$request_url = "{$this->url}&api_action=campaign_list&api_output={$this->output}&{$params}";
|
37 |
-
$response = $this->curl($request_url);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function paginator($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=campaign_paginator&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function report_bounce_list($params) {
|
48 |
-
$request_url = "{$this->url}&api_action=campaign_report_bounce_list&api_output={$this->output}&{$params}";
|
49 |
-
$response = $this->curl($request_url);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function report_bounce_totals($params) {
|
54 |
-
$request_url = "{$this->url}&api_action=campaign_report_bounce_totals&api_output={$this->output}&{$params}";
|
55 |
-
$response = $this->curl($request_url);
|
56 |
-
return $response;
|
57 |
-
}
|
58 |
-
|
59 |
-
function report_forward_list($params) {
|
60 |
-
$request_url = "{$this->url}&api_action=campaign_report_forward_list&api_output={$this->output}&{$params}";
|
61 |
-
$response = $this->curl($request_url);
|
62 |
-
return $response;
|
63 |
-
}
|
64 |
-
|
65 |
-
function report_forward_totals($params) {
|
66 |
-
$request_url = "{$this->url}&api_action=campaign_report_forward_totals&api_output={$this->output}&{$params}";
|
67 |
-
$response = $this->curl($request_url);
|
68 |
-
return $response;
|
69 |
-
}
|
70 |
-
|
71 |
-
function report_link_list($params) {
|
72 |
-
$request_url = "{$this->url}&api_action=campaign_report_link_list&api_output={$this->output}&{$params}";
|
73 |
-
$response = $this->curl($request_url);
|
74 |
-
return $response;
|
75 |
-
}
|
76 |
-
|
77 |
-
function report_link_totals($params) {
|
78 |
-
$request_url = "{$this->url}&api_action=campaign_report_link_totals&api_output={$this->output}&{$params}";
|
79 |
-
$response = $this->curl($request_url);
|
80 |
-
return $response;
|
81 |
-
}
|
82 |
-
|
83 |
-
function report_open_list($params) {
|
84 |
-
$request_url = "{$this->url}&api_action=campaign_report_open_list&api_output={$this->output}&{$params}";
|
85 |
-
$response = $this->curl($request_url);
|
86 |
-
return $response;
|
87 |
-
}
|
88 |
-
|
89 |
-
function report_open_totals($params) {
|
90 |
-
$request_url = "{$this->url}&api_action=campaign_report_open_totals&api_output={$this->output}&{$params}";
|
91 |
-
$response = $this->curl($request_url);
|
92 |
-
return $response;
|
93 |
-
}
|
94 |
-
|
95 |
-
function report_totals($params) {
|
96 |
-
$request_url = "{$this->url}&api_action=campaign_report_totals&api_output={$this->output}&{$params}";
|
97 |
-
$response = $this->curl($request_url);
|
98 |
-
return $response;
|
99 |
-
}
|
100 |
-
|
101 |
-
function report_unopen_list($params) {
|
102 |
-
$request_url = "{$this->url}&api_action=campaign_report_unopen_list&api_output={$this->output}&{$params}";
|
103 |
-
$response = $this->curl($request_url);
|
104 |
-
return $response;
|
105 |
-
}
|
106 |
-
|
107 |
-
function report_unsubscription_list($params) {
|
108 |
-
$request_url = "{$this->url}&api_action=campaign_report_unsubscription_list&api_output={$this->output}&{$params}";
|
109 |
-
$response = $this->curl($request_url);
|
110 |
-
return $response;
|
111 |
-
}
|
112 |
-
|
113 |
-
function report_unsubscription_totals($params) {
|
114 |
-
$request_url = "{$this->url}&api_action=campaign_report_unsubscription_totals&api_output={$this->output}&{$params}";
|
115 |
-
$response = $this->curl($request_url);
|
116 |
-
return $response;
|
117 |
-
}
|
118 |
-
|
119 |
-
function send($params) {
|
120 |
-
$request_url = "{$this->url}&api_action=campaign_send&api_output={$this->output}&{$params}";
|
121 |
-
$response = $this->curl($request_url);
|
122 |
-
return $response;
|
123 |
-
}
|
124 |
-
|
125 |
-
function status($params) {
|
126 |
-
$request_url = "{$this->url}&api_action=campaign_status&api_output={$this->output}&{$params}";
|
127 |
-
$response = $this->curl($request_url);
|
128 |
-
return $response;
|
129 |
-
}
|
130 |
-
|
131 |
-
}
|
132 |
-
|
133 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Connector.class.php
DELETED
@@ -1,361 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
require_once(dirname(__FILE__) . "/exceptions/RequestException.php");
|
4 |
-
|
5 |
-
/**
|
6 |
-
* Class AC_Connector
|
7 |
-
*/
|
8 |
-
class AC_Connector {
|
9 |
-
|
10 |
-
/**
|
11 |
-
* Default curl timeout
|
12 |
-
*/
|
13 |
-
const DEFAULT_TIMEOUT = 30;
|
14 |
-
|
15 |
-
/**
|
16 |
-
* @var string
|
17 |
-
*/
|
18 |
-
public $url;
|
19 |
-
|
20 |
-
/**
|
21 |
-
* @var
|
22 |
-
*/
|
23 |
-
public $api_key;
|
24 |
-
|
25 |
-
/**
|
26 |
-
* @var string
|
27 |
-
*/
|
28 |
-
public $output = "json";
|
29 |
-
|
30 |
-
/**
|
31 |
-
* @var int
|
32 |
-
*/
|
33 |
-
private $timeout = self::DEFAULT_TIMEOUT;
|
34 |
-
|
35 |
-
/**
|
36 |
-
* AC_Connector constructor.
|
37 |
-
*
|
38 |
-
* @param $url
|
39 |
-
* @param $api_key
|
40 |
-
* @param string $api_user
|
41 |
-
* @param string $api_pass
|
42 |
-
*/
|
43 |
-
function __construct($url, $api_key, $api_user = "", $api_pass = "") {
|
44 |
-
// $api_pass should be md5() already
|
45 |
-
$base = "";
|
46 |
-
if (!preg_match("/https:\/\/www.activecampaign.com/", $url)) {
|
47 |
-
// not a reseller
|
48 |
-
$base = "/admin";
|
49 |
-
}
|
50 |
-
if (preg_match("/\/$/", $url)) {
|
51 |
-
// remove trailing slash
|
52 |
-
$url = substr($url, 0, strlen($url) - 1);
|
53 |
-
}
|
54 |
-
if ($api_key) {
|
55 |
-
$this->url = "{$url}{$base}/api.php?api_key={$api_key}";
|
56 |
-
}
|
57 |
-
elseif ($api_user && $api_pass) {
|
58 |
-
$this->url = "{$url}{$base}/api.php?api_user={$api_user}&api_pass={$api_pass}";
|
59 |
-
}
|
60 |
-
$this->api_key = $api_key;
|
61 |
-
}
|
62 |
-
|
63 |
-
/**
|
64 |
-
* Test the api credentials
|
65 |
-
*
|
66 |
-
* @return bool|mixed
|
67 |
-
* @throws \RequestException
|
68 |
-
*/
|
69 |
-
public function credentials_test() {
|
70 |
-
$test_url = "{$this->url}&api_action=user_me&api_output={$this->output}";
|
71 |
-
$r = $this->curl($test_url);
|
72 |
-
if (is_object($r) && (int)$r->result_code) {
|
73 |
-
// successful
|
74 |
-
$r = true;
|
75 |
-
} else {
|
76 |
-
// failed - log it
|
77 |
-
$this->curl_response_error = $r;
|
78 |
-
$r = false;
|
79 |
-
}
|
80 |
-
return $r;
|
81 |
-
}
|
82 |
-
|
83 |
-
/**
|
84 |
-
* Debug helper function
|
85 |
-
*
|
86 |
-
* @param $var
|
87 |
-
* @param int $continue
|
88 |
-
* @param string $element
|
89 |
-
* @param string $extra
|
90 |
-
*/
|
91 |
-
public function dbg($var, $continue = 0, $element = "pre", $extra = "") {
|
92 |
-
echo "<" . $element . ">";
|
93 |
-
echo "Vartype: " . gettype($var) . "\n";
|
94 |
-
if ( is_array($var) ) echo "Elements: " . count($var) . "\n";
|
95 |
-
elseif ( is_string($var) ) echo "Length: " . strlen($var) . "\n";
|
96 |
-
if ($extra) {
|
97 |
-
echo $extra . "\n";
|
98 |
-
}
|
99 |
-
echo "\n";
|
100 |
-
print_r($var);
|
101 |
-
echo "</" . $element . ">";
|
102 |
-
if (!$continue) exit();
|
103 |
-
}
|
104 |
-
|
105 |
-
/**
|
106 |
-
* Set curl timeout
|
107 |
-
*
|
108 |
-
* @param $seconds
|
109 |
-
*/
|
110 |
-
public function set_curl_timeout($seconds) {
|
111 |
-
$this->timeout = $seconds;
|
112 |
-
}
|
113 |
-
|
114 |
-
/**
|
115 |
-
* Get curl timeout
|
116 |
-
*
|
117 |
-
* @return int
|
118 |
-
*/
|
119 |
-
public function get_curl_timeout() {
|
120 |
-
return $this->timeout;
|
121 |
-
}
|
122 |
-
|
123 |
-
/**
|
124 |
-
* Make the curl request
|
125 |
-
*
|
126 |
-
* @param $url
|
127 |
-
* @param array $params_data
|
128 |
-
* @param string $verb
|
129 |
-
* @param string $custom_method
|
130 |
-
*
|
131 |
-
* @return mixed
|
132 |
-
* @throws \RequestException
|
133 |
-
*/
|
134 |
-
public function curl($url, $params_data = array(), $verb = "", $custom_method = "") {
|
135 |
-
if ($this->version == 1) {
|
136 |
-
// find the method from the URL.
|
137 |
-
$method = preg_match("/api_action=[^&]*/i", $url, $matches);
|
138 |
-
if ($matches) {
|
139 |
-
$method = preg_match("/[^=]*$/i", $matches[0], $matches2);
|
140 |
-
$method = $matches2[0];
|
141 |
-
} elseif ($custom_method) {
|
142 |
-
$method = $custom_method;
|
143 |
-
}
|
144 |
-
} elseif ($this->version == 2) {
|
145 |
-
$method = $custom_method;
|
146 |
-
$url .= "?api_key=" . $this->api_key;
|
147 |
-
}
|
148 |
-
|
149 |
-
$debug_str1 = "";
|
150 |
-
|
151 |
-
$request = curl_init();
|
152 |
-
|
153 |
-
$debug_str1 .= "\$ch = curl_init();\n";
|
154 |
-
|
155 |
-
curl_setopt($request, CURLOPT_HEADER, 0);
|
156 |
-
curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
|
157 |
-
curl_setopt($request, CURLOPT_TIMEOUT, $this->timeout);
|
158 |
-
|
159 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_HEADER, 0);\n";
|
160 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);\n";
|
161 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_TIMEOUT, " . $this->timeout . ");\n";
|
162 |
-
|
163 |
-
if ($params_data && $verb == "GET") {
|
164 |
-
if ($this->version == 2) {
|
165 |
-
$url .= "&" . $params_data;
|
166 |
-
curl_setopt($request, CURLOPT_URL, $url);
|
167 |
-
}
|
168 |
-
}
|
169 |
-
else {
|
170 |
-
curl_setopt($request, CURLOPT_URL, $url);
|
171 |
-
if ($params_data && !$verb) {
|
172 |
-
// if no verb passed but there IS params data, it's likely POST.
|
173 |
-
$verb = "POST";
|
174 |
-
} elseif ($params_data && $verb) {
|
175 |
-
// $verb is likely "POST" or "PUT".
|
176 |
-
} else {
|
177 |
-
$verb = "GET";
|
178 |
-
}
|
179 |
-
}
|
180 |
-
|
181 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_URL, \"" . $url . "\");\n";
|
182 |
-
|
183 |
-
if ($this->debug) {
|
184 |
-
$this->dbg($url, 1, "pre", "Description: Request URL");
|
185 |
-
}
|
186 |
-
|
187 |
-
if ($verb == "POST" || $verb == "PUT" || $verb == "DELETE") {
|
188 |
-
if ($verb == "PUT") {
|
189 |
-
curl_setopt($request, CURLOPT_CUSTOMREQUEST, "PUT");
|
190 |
-
|
191 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_CUSTOMREQUEST, \"PUT\");\n";
|
192 |
-
} elseif ($verb == "DELETE") {
|
193 |
-
curl_setopt($request, CURLOPT_CUSTOMREQUEST, "DELETE");
|
194 |
-
|
195 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_CUSTOMREQUEST, \"DELETE\");\n";
|
196 |
-
} else {
|
197 |
-
$verb = "POST";
|
198 |
-
curl_setopt($request, CURLOPT_POST, 1);
|
199 |
-
|
200 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_POST, 1);\n";
|
201 |
-
}
|
202 |
-
$data = "";
|
203 |
-
if (is_array($params_data)) {
|
204 |
-
foreach($params_data as $key => $value) {
|
205 |
-
if (is_array($value)) {
|
206 |
-
|
207 |
-
if (is_int($key)) {
|
208 |
-
// array two levels deep
|
209 |
-
foreach ($value as $key_ => $value_) {
|
210 |
-
if (is_array($value_)) {
|
211 |
-
foreach ($value_ as $k => $v) {
|
212 |
-
$k = urlencode($k);
|
213 |
-
$data .= "{$key_}[{$key}][{$k}]=" . urlencode($v) . "&";
|
214 |
-
}
|
215 |
-
} else {
|
216 |
-
$data .= "{$key_}[{$key}]=" . urlencode($value_) . "&";
|
217 |
-
}
|
218 |
-
}
|
219 |
-
} elseif (preg_match('/^field\[.*,0\]/', $key)) {
|
220 |
-
// if the $key is that of a field and the $value is that of an array
|
221 |
-
if (is_array($value)) {
|
222 |
-
// then join the values with double pipes
|
223 |
-
$value = implode('||', $value);
|
224 |
-
}
|
225 |
-
$data .= "{$key}=" . urlencode($value) . "&";
|
226 |
-
} else {
|
227 |
-
// IE: [group] => array(2 => 2, 3 => 3)
|
228 |
-
// normally we just want the key to be a string, IE: ["group[2]"] => 2
|
229 |
-
// but we want to allow passing both formats
|
230 |
-
foreach ($value as $k => $v) {
|
231 |
-
if (!is_array($v)) {
|
232 |
-
$k = urlencode($k);
|
233 |
-
$data .= "{$key}[{$k}]=" . urlencode($v) . "&";
|
234 |
-
}
|
235 |
-
}
|
236 |
-
}
|
237 |
-
|
238 |
-
} else {
|
239 |
-
$data .= "{$key}=" . urlencode($value) . "&";
|
240 |
-
}
|
241 |
-
}
|
242 |
-
} else {
|
243 |
-
// not an array - perhaps serialized or JSON string?
|
244 |
-
// just pass it as data
|
245 |
-
$data = "data={$params_data}";
|
246 |
-
}
|
247 |
-
|
248 |
-
$data = rtrim($data, "& ");
|
249 |
-
|
250 |
-
curl_setopt($request, CURLOPT_HTTPHEADER, array("Expect:"));
|
251 |
-
|
252 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_HTTPHEADER, array(\"Expect:\"));\n";
|
253 |
-
|
254 |
-
if ($this->debug) {
|
255 |
-
curl_setopt($request, CURLINFO_HEADER_OUT, 1);
|
256 |
-
|
257 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLINFO_HEADER_OUT, 1);\n";
|
258 |
-
|
259 |
-
$this->dbg($data, 1, "pre", "Description: POST data");
|
260 |
-
}
|
261 |
-
|
262 |
-
curl_setopt($request, CURLOPT_POSTFIELDS, $data);
|
263 |
-
|
264 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_POSTFIELDS, \"" . $data . "\");\n";
|
265 |
-
}
|
266 |
-
|
267 |
-
curl_setopt($request, CURLOPT_SSL_VERIFYPEER, false);
|
268 |
-
curl_setopt($request, CURLOPT_SSL_VERIFYHOST, 0);
|
269 |
-
|
270 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_SSL_VERIFYPEER, false);\n";
|
271 |
-
$debug_str1 .= "curl_setopt(\$ch, CURLOPT_SSL_VERIFYHOST, 0);\n";
|
272 |
-
|
273 |
-
$response = curl_exec($request);
|
274 |
-
|
275 |
-
$curl_error = curl_error($request);
|
276 |
-
|
277 |
-
if (!$response && $curl_error) {
|
278 |
-
return $curl_error;
|
279 |
-
}
|
280 |
-
|
281 |
-
$debug_str1 .= "curl_exec(\$ch);\n";
|
282 |
-
|
283 |
-
if ($this->debug) {
|
284 |
-
$this->dbg($response, 1, "pre", "Description: Raw response");
|
285 |
-
}
|
286 |
-
|
287 |
-
$http_code = curl_getinfo($request, CURLINFO_HTTP_CODE);
|
288 |
-
if (!preg_match("/^[2-3][0-9]{2}/", $http_code)) {
|
289 |
-
// If not 200 or 300 range HTTP code, return custom error.
|
290 |
-
return "HTTP code $http_code returned";
|
291 |
-
}
|
292 |
-
|
293 |
-
$debug_str1 .= "\$http_code = curl_getinfo(\$ch, CURLINFO_HTTP_CODE);\n";
|
294 |
-
|
295 |
-
if ($this->debug) {
|
296 |
-
$this->dbg($http_code, 1, "pre", "Description: Response HTTP code");
|
297 |
-
|
298 |
-
$request_headers = curl_getinfo($request, CURLINFO_HEADER_OUT);
|
299 |
-
|
300 |
-
$debug_str1 .= "\$request_headers = curl_getinfo(\$ch, CURLINFO_HEADER_OUT);\n";
|
301 |
-
|
302 |
-
$this->dbg($request_headers, 1, "pre", "Description: Request headers");
|
303 |
-
}
|
304 |
-
|
305 |
-
curl_close($request);
|
306 |
-
|
307 |
-
$debug_str1 .= "curl_close(\$ch);\n";
|
308 |
-
|
309 |
-
$object = json_decode($response);
|
310 |
-
|
311 |
-
if ($this->debug) {
|
312 |
-
$this->dbg($object, 1, "pre", "Description: Response object (json_decode)");
|
313 |
-
}
|
314 |
-
if ( !is_object($object) || (!isset($object->result_code) && !isset($object->succeeded) && !isset($object->success)) ) {
|
315 |
-
// add methods that only return a string
|
316 |
-
$string_responses = array("tags_list", "segment_list", "tracking_event_remove", "contact_list", "form_html", "tracking_site_status", "tracking_event_status", "tracking_whitelist", "tracking_log", "tracking_site_list", "tracking_event_list");
|
317 |
-
if (in_array($method, $string_responses)) {
|
318 |
-
return $response;
|
319 |
-
}
|
320 |
-
|
321 |
-
$this->throwRequestException($response);
|
322 |
-
}
|
323 |
-
|
324 |
-
if ($this->debug) {
|
325 |
-
echo "<textarea style='height: 300px; width: 600px;'>" . $debug_str1 . "</textarea>";
|
326 |
-
}
|
327 |
-
|
328 |
-
$object->http_code = $http_code;
|
329 |
-
|
330 |
-
if (isset($object->result_code)) {
|
331 |
-
$object->success = $object->result_code;
|
332 |
-
|
333 |
-
if (!(int)$object->result_code) {
|
334 |
-
$object->error = $object->result_message;
|
335 |
-
}
|
336 |
-
} elseif (isset($object->succeeded)) {
|
337 |
-
// some calls return "succeeded" only
|
338 |
-
$object->success = $object->succeeded;
|
339 |
-
|
340 |
-
if (!(int)$object->succeeded) {
|
341 |
-
$object->error = $object->message;
|
342 |
-
}
|
343 |
-
}
|
344 |
-
|
345 |
-
return $object;
|
346 |
-
}
|
347 |
-
|
348 |
-
/**
|
349 |
-
* Throw the request exception
|
350 |
-
*
|
351 |
-
* @param $message
|
352 |
-
*
|
353 |
-
* @throws \RequestException
|
354 |
-
*/
|
355 |
-
protected function throwRequestException($message) {
|
356 |
-
$requestException = new RequestException;
|
357 |
-
$requestException->setFailedMessage($message);
|
358 |
-
|
359 |
-
throw $requestException;
|
360 |
-
}
|
361 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Contact.class.php
DELETED
@@ -1,127 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Contact extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=contact_add&api_output={$this->output}";
|
19 |
-
if ($params) $request_url .= "&{$params}";
|
20 |
-
$response = $this->curl($request_url, $post_data);
|
21 |
-
return $response;
|
22 |
-
}
|
23 |
-
|
24 |
-
function automation_list($params) {
|
25 |
-
$request_url = "{$this->url}&api_action=contact_automation_list&api_output={$this->output}&{$params}";
|
26 |
-
$response = $this->curl($request_url);
|
27 |
-
return $response;
|
28 |
-
}
|
29 |
-
|
30 |
-
function delete_list($params) {
|
31 |
-
$request_url = "{$this->url}&api_action=contact_delete_list&api_output={$this->output}&{$params}";
|
32 |
-
$response = $this->curl($request_url);
|
33 |
-
return $response;
|
34 |
-
}
|
35 |
-
|
36 |
-
function delete($params) {
|
37 |
-
$request_url = "{$this->url}&api_action=contact_delete&api_output={$this->output}&{$params}";
|
38 |
-
$response = $this->curl($request_url);
|
39 |
-
return $response;
|
40 |
-
}
|
41 |
-
|
42 |
-
function edit($params, $post_data) {
|
43 |
-
$request_url = "{$this->url}&api_action=contact_edit&api_output={$this->output}&{$params}";
|
44 |
-
$response = $this->curl($request_url, $post_data);
|
45 |
-
return $response;
|
46 |
-
}
|
47 |
-
|
48 |
-
function list_($params) {
|
49 |
-
if ($this->version == 1) {
|
50 |
-
$request_url = "{$this->url}&api_action=contact_list&api_output={$this->output}&{$params}";
|
51 |
-
$response = $this->curl($request_url);
|
52 |
-
} elseif ($this->version == 2) {
|
53 |
-
$request_url = "{$this->url_base}/contact/emails";
|
54 |
-
// $params example: offset=0&limit=1000&listid=4
|
55 |
-
$response = $this->curl($request_url, $params, "GET", "contact_list");
|
56 |
-
}
|
57 |
-
return $response;
|
58 |
-
}
|
59 |
-
|
60 |
-
function note_add($params, $post_data) {
|
61 |
-
$request_url = "{$this->url}&api_action=contact_note_add&api_output={$this->output}&{$params}";
|
62 |
-
$response = $this->curl($request_url, $post_data);
|
63 |
-
return $response;
|
64 |
-
}
|
65 |
-
|
66 |
-
function note_edit($params, $post_data) {
|
67 |
-
$request_url = "{$this->url}&api_action=contact_note_edit&api_output={$this->output}&{$params}";
|
68 |
-
$response = $this->curl($request_url, $post_data);
|
69 |
-
return $response;
|
70 |
-
}
|
71 |
-
|
72 |
-
function note_delete($params) {
|
73 |
-
$request_url = "{$this->url}&api_action=contact_note_delete&api_output={$this->output}&{$params}";
|
74 |
-
$response = $this->curl($request_url);
|
75 |
-
return $response;
|
76 |
-
}
|
77 |
-
|
78 |
-
function paginator($params) {
|
79 |
-
$request_url = "{$this->url}&api_action=contact_paginator&api_output={$this->output}&{$params}";
|
80 |
-
$response = $this->curl($request_url);
|
81 |
-
return $response;
|
82 |
-
}
|
83 |
-
|
84 |
-
function sync($params, $post_data) {
|
85 |
-
$request_url = "{$this->url}&api_action=contact_sync&api_output={$this->output}";
|
86 |
-
if ($params) $request_url .= "&{$params}";
|
87 |
-
$response = $this->curl($request_url, $post_data);
|
88 |
-
return $response;
|
89 |
-
}
|
90 |
-
|
91 |
-
function tag_add($params, $post_data) {
|
92 |
-
$request_url = "{$this->url}&api_action=contact_tag_add&api_output={$this->output}";
|
93 |
-
if ($params) $request_url .= "&{$params}";
|
94 |
-
$response = $this->curl($request_url, $post_data);
|
95 |
-
return $response;
|
96 |
-
}
|
97 |
-
|
98 |
-
function tag_remove($params, $post_data) {
|
99 |
-
$request_url = "{$this->url}&api_action=contact_tag_remove&api_output={$this->output}";
|
100 |
-
if ($params) $request_url .= "&{$params}";
|
101 |
-
$response = $this->curl($request_url, $post_data);
|
102 |
-
return $response;
|
103 |
-
}
|
104 |
-
|
105 |
-
function view($params) {
|
106 |
-
// can be a contact ID, email, or hash
|
107 |
-
if (preg_match("/^email=/", $params)) {
|
108 |
-
$action = "contact_view_email";
|
109 |
-
}
|
110 |
-
elseif (preg_match("/^hash=/", $params)) {
|
111 |
-
$action = "contact_view_hash";
|
112 |
-
}
|
113 |
-
elseif (preg_match("/^id=/", $params)) {
|
114 |
-
$action = "contact_view";
|
115 |
-
}
|
116 |
-
else {
|
117 |
-
// default
|
118 |
-
$action = "contact_view";
|
119 |
-
}
|
120 |
-
$request_url = "{$this->url}&api_action={$action}&api_output={$this->output}&{$params}";
|
121 |
-
$response = $this->curl($request_url);
|
122 |
-
return $response;
|
123 |
-
}
|
124 |
-
|
125 |
-
}
|
126 |
-
|
127 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Deal.class.php
DELETED
@@ -1,139 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Deal extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=deal_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function edit($params, $post_data) {
|
24 |
-
$request_url = "{$this->url}&api_action=deal_edit&api_output={$this->output}";
|
25 |
-
$response = $this->curl($request_url, $post_data);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete($params, $post_data) {
|
30 |
-
$request_url = "{$this->url}&api_action=deal_delete&api_output={$this->output}";
|
31 |
-
$response = $this->curl($request_url, $post_data);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function get($params) {
|
36 |
-
$request_url = "{$this->url}&api_action=deal_get&api_output={$this->output}&{$params}";
|
37 |
-
$response = $this->curl($request_url);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function list_($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=deal_list&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function note_add($params, $post_data) {
|
48 |
-
$request_url = "{$this->url}&api_action=deal_note_add&api_output={$this->output}";
|
49 |
-
$response = $this->curl($request_url, $post_data);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function note_edit($params, $post_data) {
|
54 |
-
$request_url = "{$this->url}&api_action=deal_note_edit&api_output={$this->output}";
|
55 |
-
$response = $this->curl($request_url, $post_data);
|
56 |
-
return $response;
|
57 |
-
}
|
58 |
-
|
59 |
-
function pipeline_add($params, $post_data) {
|
60 |
-
$request_url = "{$this->url}&api_action=deal_pipeline_add&api_output={$this->output}";
|
61 |
-
$response = $this->curl($request_url, $post_data);
|
62 |
-
return $response;
|
63 |
-
}
|
64 |
-
|
65 |
-
function pipeline_edit($params, $post_data) {
|
66 |
-
$request_url = "{$this->url}&api_action=deal_pipeline_edit&api_output={$this->output}";
|
67 |
-
$response = $this->curl($request_url, $post_data);
|
68 |
-
return $response;
|
69 |
-
}
|
70 |
-
|
71 |
-
function pipeline_delete($params, $post_data) {
|
72 |
-
$request_url = "{$this->url}&api_action=deal_pipeline_delete&api_output={$this->output}";
|
73 |
-
$response = $this->curl($request_url, $post_data);
|
74 |
-
return $response;
|
75 |
-
}
|
76 |
-
|
77 |
-
function pipeline_list($params) {
|
78 |
-
$request_url = "{$this->url}&api_action=deal_pipeline_list&api_output={$this->output}&{$params}";
|
79 |
-
$response = $this->curl($request_url);
|
80 |
-
return $response;
|
81 |
-
}
|
82 |
-
|
83 |
-
function stage_add($params, $post_data) {
|
84 |
-
$request_url = "{$this->url}&api_action=deal_stage_add&api_output={$this->output}";
|
85 |
-
$response = $this->curl($request_url, $post_data);
|
86 |
-
return $response;
|
87 |
-
}
|
88 |
-
|
89 |
-
function stage_edit($params, $post_data) {
|
90 |
-
$request_url = "{$this->url}&api_action=deal_stage_edit&api_output={$this->output}";
|
91 |
-
$response = $this->curl($request_url, $post_data);
|
92 |
-
return $response;
|
93 |
-
}
|
94 |
-
|
95 |
-
function stage_delete($params, $post_data) {
|
96 |
-
$request_url = "{$this->url}&api_action=deal_stage_delete&api_output={$this->output}";
|
97 |
-
$response = $this->curl($request_url, $post_data);
|
98 |
-
return $response;
|
99 |
-
}
|
100 |
-
|
101 |
-
function stage_list($params) {
|
102 |
-
$request_url = "{$this->url}&api_action=deal_stage_list&api_output={$this->output}&{$params}";
|
103 |
-
$response = $this->curl($request_url);
|
104 |
-
return $response;
|
105 |
-
}
|
106 |
-
|
107 |
-
function task_add($params, $post_data) {
|
108 |
-
$request_url = "{$this->url}&api_action=deal_task_add&api_output={$this->output}";
|
109 |
-
$response = $this->curl($request_url, $post_data);
|
110 |
-
return $response;
|
111 |
-
}
|
112 |
-
|
113 |
-
function task_edit($params, $post_data) {
|
114 |
-
$request_url = "{$this->url}&api_action=deal_task_edit&api_output={$this->output}";
|
115 |
-
$response = $this->curl($request_url, $post_data);
|
116 |
-
return $response;
|
117 |
-
}
|
118 |
-
|
119 |
-
function tasktype_add($params, $post_data) {
|
120 |
-
$request_url = "{$this->url}&api_action=deal_tasktype_add&api_output={$this->output}";
|
121 |
-
$response = $this->curl($request_url, $post_data);
|
122 |
-
return $response;
|
123 |
-
}
|
124 |
-
|
125 |
-
function tasktype_edit($params, $post_data) {
|
126 |
-
$request_url = "{$this->url}&api_action=deal_tasktype_edit&api_output={$this->output}";
|
127 |
-
$response = $this->curl($request_url, $post_data);
|
128 |
-
return $response;
|
129 |
-
}
|
130 |
-
|
131 |
-
function tasktype_delete($params, $post_data) {
|
132 |
-
$request_url = "{$this->url}&api_action=deal_tasktype_delete&api_output={$this->output}";
|
133 |
-
$response = $this->curl($request_url, $post_data);
|
134 |
-
return $response;
|
135 |
-
}
|
136 |
-
|
137 |
-
}
|
138 |
-
|
139 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Design.class.php
DELETED
@@ -1,31 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Design extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function edit($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=branding_edit&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function view($params, $post_data) {
|
24 |
-
$request_url = "{$this->url}&api_action=branding_view&api_output={$this->output}";
|
25 |
-
$response = $this->curl($request_url, $post_data);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
}
|
30 |
-
|
31 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Form.class.php
DELETED
@@ -1,298 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Form extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function getforms($params) {
|
18 |
-
$request_url = "{$this->url}&api_action=form_getforms&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function html($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=form_html&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function embed($params) {
|
30 |
-
|
31 |
-
$params_array = explode("&", $params);
|
32 |
-
$params_ = array();
|
33 |
-
foreach ($params_array as $expression) {
|
34 |
-
// IE: css=1
|
35 |
-
list($var, $val) = explode("=", $expression);
|
36 |
-
$params_[$var] = $val;
|
37 |
-
}
|
38 |
-
|
39 |
-
$id = (isset($params_["id"])) ? (int)$params_["id"] : 0;
|
40 |
-
$css = (isset($params_["css"])) ? (int)$params_["css"] : 1;
|
41 |
-
$ajax = (isset($params_["ajax"])) ? (int)$params_["ajax"] : 0;
|
42 |
-
// to set the current page as the action, pass "action=", or "action=[THIS URL]"
|
43 |
-
$action = (isset($params_["action"])) ? ($params_["action"] ? $params_["action"] : "this") : "";
|
44 |
-
|
45 |
-
$html = $this->html("id={$id}");
|
46 |
-
|
47 |
-
if (is_object($html) && !(int)$html->success) {
|
48 |
-
return $html->error;
|
49 |
-
}
|
50 |
-
|
51 |
-
if ($html) {
|
52 |
-
|
53 |
-
if ($action) {
|
54 |
-
if ($action != "this") {
|
55 |
-
// replace the action attribute with the one provided
|
56 |
-
$action_val = urldecode($action);
|
57 |
-
$html = preg_replace("/action=['\"][^'\"]+['\"]/", "action='{$action_val}'", $html);
|
58 |
-
}
|
59 |
-
else {
|
60 |
-
$action_val = "";
|
61 |
-
}
|
62 |
-
}
|
63 |
-
else {
|
64 |
-
// find the action attribute value (URL)
|
65 |
-
// should be the proc.php URL (at this point in the script)
|
66 |
-
$action_val = preg_match("/action=['\"][^'\"]+['\"]/", $html, $m);
|
67 |
-
$action_val = $m[0];
|
68 |
-
$action_val = substr($action_val, 8, strlen($action_val) - 9);
|
69 |
-
}
|
70 |
-
|
71 |
-
if (!$css) {
|
72 |
-
// remove all CSS
|
73 |
-
$html = preg_replace("/<style[^>]*>(.*)<\/style>/s", "", $html);
|
74 |
-
}
|
75 |
-
|
76 |
-
if (!$ajax) {
|
77 |
-
// replace the Submit button to be an actual submit type
|
78 |
-
$html = preg_replace("/input type='button'/", "input type='submit'", $html);
|
79 |
-
|
80 |
-
// if action = "this", remove the action attribute completely
|
81 |
-
if (!$action_val) {
|
82 |
-
$html = preg_replace("/action=['\"][^'\"]+['\"]/", "", $html);
|
83 |
-
}
|
84 |
-
}
|
85 |
-
else {
|
86 |
-
|
87 |
-
// if using Ajax, remove the <form> action attribute completely
|
88 |
-
$html = preg_replace("/action=['\"][^'\"]+['\"]/", "", $html);
|
89 |
-
|
90 |
-
// replace the Submit button to be a button type (for ajax).
|
91 |
-
// forms come out of AC now with a "submit" button (it used to be "button").
|
92 |
-
$html = preg_replace("/class=['\"]+_submit['\"]+ type=['\"]+submit['\"]+/", "class='_submit' type='button'", $html);
|
93 |
-
|
94 |
-
// Replace the external image (captcha) script with the local one, so the session var is accessible.
|
95 |
-
$html = preg_replace("/\/\/.*\/ac_global\/scripts\/randomimage\.php/i", "randomimage.php", $html);
|
96 |
-
|
97 |
-
// Remove Embedded forms JS
|
98 |
-
$html = preg_replace('/<script[^>]*>.*?<\/script>/s', '', $html);
|
99 |
-
|
100 |
-
$action_val = urldecode($action_val);
|
101 |
-
|
102 |
-
// add jQuery stuff
|
103 |
-
$extra = "<script type='text/javascript'>
|
104 |
-
|
105 |
-
var \$j = jQuery.noConflict();
|
106 |
-
|
107 |
-
\$j(document).ready(function() {
|
108 |
-
|
109 |
-
\$j('#_form_{$id}_ button').click(function() {
|
110 |
-
|
111 |
-
// rename the radio options for Subscribe/Unsubscribe, since they conflict with the hidden field.
|
112 |
-
\$j('input[type=radio][name=act]').attr('name','act_radio');
|
113 |
-
|
114 |
-
var form_data = {};
|
115 |
-
\$j('#_form_{$id}_').each(function() {
|
116 |
-
form_data = \$j(this).serialize();
|
117 |
-
});
|
118 |
-
|
119 |
-
var geturl;
|
120 |
-
geturl = \$j.ajax({
|
121 |
-
url: '{$action_val}',
|
122 |
-
type: 'POST',
|
123 |
-
dataType: 'json',
|
124 |
-
data: form_data,
|
125 |
-
error: function(jqXHR, textStatus, errorThrown) {
|
126 |
-
console.log(errorThrown);
|
127 |
-
},
|
128 |
-
success: function(data) {
|
129 |
-
\$j('#form_result_message').html(data.message);
|
130 |
-
var result_class = (data.success) ? 'form_result_success' : 'form_result_error';
|
131 |
-
\$j('#form_result_message').removeClass('form_result_success form_result_error').addClass(result_class);
|
132 |
-
}
|
133 |
-
});
|
134 |
-
|
135 |
-
});
|
136 |
-
|
137 |
-
});
|
138 |
-
|
139 |
-
</script>";
|
140 |
-
|
141 |
-
$html = $html . $extra;
|
142 |
-
}
|
143 |
-
|
144 |
-
}
|
145 |
-
|
146 |
-
return $html;
|
147 |
-
}
|
148 |
-
|
149 |
-
function process($params) {
|
150 |
-
$r = array();
|
151 |
-
if ($_SERVER["REQUEST_METHOD"] != "POST") return $r;
|
152 |
-
|
153 |
-
$sync = 0;
|
154 |
-
$captcha_in_form = 0;
|
155 |
-
if ($params) {
|
156 |
-
$params_array = explode("&", $params);
|
157 |
-
$params_ = array();
|
158 |
-
foreach ($params_array as $expression) {
|
159 |
-
// IE: css=1
|
160 |
-
list($var, $val) = explode("=", $expression);
|
161 |
-
$params_[$var] = $val;
|
162 |
-
}
|
163 |
-
|
164 |
-
$sync = (isset($params_["sync"])) ? (int)$params_["sync"] : 0;
|
165 |
-
$captcha_in_form = (isset($params_["captcha"])) ? (int)$params_["captcha"] : 0;
|
166 |
-
}
|
167 |
-
|
168 |
-
$formid = $_POST["f"];
|
169 |
-
// sub or unsub
|
170 |
-
$act = isset($_POST["act"]) ? $_POST["act"] : "sub";
|
171 |
-
if (isset($_POST["act_radio"])) {
|
172 |
-
// the radio options for Subscribe/Unsubscribe.
|
173 |
-
$act = $_POST["act_radio"];
|
174 |
-
}
|
175 |
-
$email = $_POST["email"];
|
176 |
-
$firstname = $lastname = "";
|
177 |
-
$phone = isset($_POST["phone"]) ? $_POST["phone"] : "";
|
178 |
-
$lists = (isset($_POST["nlbox"]) && $_POST["nlbox"]) ? $_POST["nlbox"] : array();
|
179 |
-
if ($captcha_in_form) {
|
180 |
-
// Captcha is part of the form.
|
181 |
-
// Get the captcha value the user entered.
|
182 |
-
$captcha = "";
|
183 |
-
if (isset($_POST["captcha"])) {
|
184 |
-
$captcha = md5(strtoupper((string)$_POST["captcha"]));
|
185 |
-
}
|
186 |
-
if (!isset($_SESSION["image_random_value"]) || !isset($_SESSION["image_random_value"][$captcha])) {
|
187 |
-
return json_encode(array("success" => 0, "message" => "Invalid captcha"));
|
188 |
-
}
|
189 |
-
}
|
190 |
-
|
191 |
-
if (isset($_POST["fullname"])) {
|
192 |
-
$fullname = explode(" ", $_POST["fullname"]);
|
193 |
-
$firstname = array_shift($fullname);
|
194 |
-
$lastname = implode(" ", $fullname);
|
195 |
-
}
|
196 |
-
elseif (isset($_POST["firstname"]) && isset($_POST["lastname"])) {
|
197 |
-
$firstname = trim($_POST["firstname"]);
|
198 |
-
$lastname = trim($_POST["lastname"]);
|
199 |
-
if ($firstname == "" && isset($_POST["first_name"])) $firstname = trim($_POST["first_name"]);
|
200 |
-
if ($lastname == "" && isset($_POST["last_name"])) $lastname = trim($_POST["last_name"]);
|
201 |
-
}
|
202 |
-
|
203 |
-
$fields = (isset($_POST["field"])) ? $_POST["field"] : array();
|
204 |
-
|
205 |
-
$contact = array(
|
206 |
-
"form" => $formid,
|
207 |
-
"email" => $email,
|
208 |
-
"first_name" => $firstname,
|
209 |
-
"last_name" => $lastname,
|
210 |
-
"phone" => $phone,
|
211 |
-
);
|
212 |
-
|
213 |
-
foreach ($fields as $ac_field_id => $field_value) {
|
214 |
-
$contact["field"][$ac_field_id . ",0"] = $field_value;
|
215 |
-
}
|
216 |
-
|
217 |
-
// add lists
|
218 |
-
$status = ($act == "unsub") ? 2 : 1;
|
219 |
-
foreach ($lists as $listid) {
|
220 |
-
$contact["p[{$listid}]"] = $listid;
|
221 |
-
$contact["status[{$listid}]"] = $status;
|
222 |
-
$contact["instantresponders[{$listid}]"] = 1;
|
223 |
-
}
|
224 |
-
|
225 |
-
if (!$sync) {
|
226 |
-
|
227 |
-
// do add/edit
|
228 |
-
|
229 |
-
$contact_exists = $this->api("contact/view?email={$email}", $contact);
|
230 |
-
|
231 |
-
if ( !isset($contact_exists->id) ) {
|
232 |
-
|
233 |
-
// contact does not exist - add them
|
234 |
-
|
235 |
-
$contact_request = $this->api("contact/add", $contact);
|
236 |
-
|
237 |
-
if ((int)$contact_request->success) {
|
238 |
-
// successful request
|
239 |
-
$contact_id = (int)$contact_request->subscriber_id;
|
240 |
-
$r = array(
|
241 |
-
"success" => 1,
|
242 |
-
"message" => $contact_request->result_message,
|
243 |
-
"contact_id" => $contact_id,
|
244 |
-
);
|
245 |
-
}
|
246 |
-
else {
|
247 |
-
// request failed
|
248 |
-
$r = array(
|
249 |
-
"success" => 0,
|
250 |
-
"message" => $contact_request->error,
|
251 |
-
);
|
252 |
-
}
|
253 |
-
|
254 |
-
}
|
255 |
-
else {
|
256 |
-
|
257 |
-
// contact already exists - edit them
|
258 |
-
|
259 |
-
$contact_id = $contact_exists->id;
|
260 |
-
|
261 |
-
$contact["id"] = $contact_id;
|
262 |
-
|
263 |
-
$contact_request = $this->api("contact/edit?overwrite=0", $contact);
|
264 |
-
|
265 |
-
}
|
266 |
-
|
267 |
-
}
|
268 |
-
else {
|
269 |
-
|
270 |
-
// perform sync (add or edit)
|
271 |
-
|
272 |
-
$contact_request = $this->api("contact/sync", $contact);
|
273 |
-
|
274 |
-
}
|
275 |
-
|
276 |
-
if ((int)$contact_request->success) {
|
277 |
-
// successful request
|
278 |
-
//$contact_id = (int)$contact_request->contact_id;
|
279 |
-
$r = array(
|
280 |
-
"success" => 1,
|
281 |
-
"message" => $contact_request->result_message,
|
282 |
-
//"contact_id" => $contact_id,
|
283 |
-
);
|
284 |
-
}
|
285 |
-
else {
|
286 |
-
// request failed
|
287 |
-
$r = array(
|
288 |
-
"success" => 0,
|
289 |
-
"message" => $contact_request->error,
|
290 |
-
);
|
291 |
-
}
|
292 |
-
|
293 |
-
return json_encode($r);
|
294 |
-
}
|
295 |
-
|
296 |
-
}
|
297 |
-
|
298 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Group.class.php
DELETED
@@ -1,55 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Group extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=group_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function delete_list($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=group_delete_list&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete($params) {
|
30 |
-
$request_url = "{$this->url}&api_action=group_delete&api_output={$this->output}&{$params}";
|
31 |
-
$response = $this->curl($request_url);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function edit($params, $post_data) {
|
36 |
-
$request_url = "{$this->url}&api_action=group_edit&api_output={$this->output}";
|
37 |
-
$response = $this->curl($request_url, $post_data);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function list_($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=group_list&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function view($params) {
|
48 |
-
$request_url = "{$this->url}&api_action=group_view&api_output={$this->output}&{$params}";
|
49 |
-
$response = $this->curl($request_url);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
}
|
54 |
-
|
55 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/List.class.php
DELETED
@@ -1,91 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_List_ extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=list_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function delete_list($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=list_delete_list&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete($params) {
|
30 |
-
$request_url = "{$this->url}&api_action=list_delete&api_output={$this->output}&{$params}";
|
31 |
-
$response = $this->curl($request_url);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function edit($params, $post_data) {
|
36 |
-
$request_url = "{$this->url}&api_action=list_edit&api_output={$this->output}";
|
37 |
-
$response = $this->curl($request_url, $post_data);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function field_add($params, $post_data) {
|
42 |
-
$request_url = "{$this->url}&api_action=list_field_add&api_output={$this->output}";
|
43 |
-
$response = $this->curl($request_url, $post_data);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function field_delete($params) {
|
48 |
-
$request_url = "{$this->url}&api_action=list_field_delete&api_output={$this->output}&{$params}";
|
49 |
-
$response = $this->curl($request_url);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function field_edit($params, $post_data) {
|
54 |
-
$request_url = "{$this->url}&api_action=list_field_edit&api_output={$this->output}";
|
55 |
-
$response = $this->curl($request_url, $post_data);
|
56 |
-
return $response;
|
57 |
-
}
|
58 |
-
|
59 |
-
function field_view($params) {
|
60 |
-
$request_url = "{$this->url}&api_action=list_field_view&api_output={$this->output}&{$params}";
|
61 |
-
$response = $this->curl($request_url);
|
62 |
-
return $response;
|
63 |
-
}
|
64 |
-
|
65 |
-
function list_($params, $post_data) {
|
66 |
-
if ($post_data) {
|
67 |
-
if (isset($post_data["ids"]) && is_array($post_data["ids"])) {
|
68 |
-
// make them comma-separated.
|
69 |
-
$post_data["ids"] = implode(",", $post_data["ids"]);
|
70 |
-
}
|
71 |
-
}
|
72 |
-
$request_url = "{$this->url}&api_action=list_list&api_output={$this->output}&{$params}";
|
73 |
-
$response = $this->curl($request_url, $post_data);
|
74 |
-
return $response;
|
75 |
-
}
|
76 |
-
|
77 |
-
function paginator($params) {
|
78 |
-
$request_url = "{$this->url}&api_action=list_paginator&api_output={$this->output}&{$params}";
|
79 |
-
$response = $this->curl($request_url);
|
80 |
-
return $response;
|
81 |
-
}
|
82 |
-
|
83 |
-
function view($params) {
|
84 |
-
$request_url = "{$this->url}&api_action=list_view&api_output={$this->output}&{$params}";
|
85 |
-
$response = $this->curl($request_url);
|
86 |
-
return $response;
|
87 |
-
}
|
88 |
-
|
89 |
-
}
|
90 |
-
|
91 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Message.class.php
DELETED
@@ -1,103 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Message extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=message_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function delete_list($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=message_delete_list&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete($params) {
|
30 |
-
$request_url = "{$this->url}&api_action=message_delete&api_output={$this->output}&{$params}";
|
31 |
-
$response = $this->curl($request_url);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function edit($params, $post_data) {
|
36 |
-
$request_url = "{$this->url}&api_action=message_edit&api_output={$this->output}";
|
37 |
-
$response = $this->curl($request_url, $post_data);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function list_($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=message_list&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function template_add($params, $post_data) {
|
48 |
-
$request_url = "{$this->url}&api_action=message_template_add&api_output={$this->output}";
|
49 |
-
$response = $this->curl($request_url, $post_data);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function template_delete_list($params) {
|
54 |
-
$request_url = "{$this->url}&api_action=message_template_delete_list&api_output={$this->output}&{$params}";
|
55 |
-
$response = $this->curl($request_url);
|
56 |
-
return $response;
|
57 |
-
}
|
58 |
-
|
59 |
-
function template_delete($params) {
|
60 |
-
$request_url = "{$this->url}&api_action=message_template_delete&api_output={$this->output}&{$params}";
|
61 |
-
$response = $this->curl($request_url);
|
62 |
-
return $response;
|
63 |
-
}
|
64 |
-
|
65 |
-
function template_edit($params, $post_data) {
|
66 |
-
$request_url = "{$this->url}&api_action=message_template_edit&api_output={$this->output}";
|
67 |
-
$response = $this->curl($request_url, $post_data);
|
68 |
-
return $response;
|
69 |
-
}
|
70 |
-
|
71 |
-
function template_export($params) {
|
72 |
-
$request_url = "{$this->url}&api_action=message_template_export&api_output={$this->output}&{$params}";
|
73 |
-
$response = $this->curl($request_url);
|
74 |
-
return $response;
|
75 |
-
}
|
76 |
-
|
77 |
-
function template_import($params, $post_data) {
|
78 |
-
$request_url = "{$this->url}&api_action=message_template_import&api_output={$this->output}";
|
79 |
-
$response = $this->curl($request_url, $post_data);
|
80 |
-
return $response;
|
81 |
-
}
|
82 |
-
|
83 |
-
function template_list($params) {
|
84 |
-
$request_url = "{$this->url}&api_action=message_template_list&api_output={$this->output}&{$params}";
|
85 |
-
$response = $this->curl($request_url);
|
86 |
-
return $response;
|
87 |
-
}
|
88 |
-
|
89 |
-
function template_view($params) {
|
90 |
-
$request_url = "{$this->url}&api_action=message_template_view&api_output={$this->output}&{$params}";
|
91 |
-
$response = $this->curl($request_url);
|
92 |
-
return $response;
|
93 |
-
}
|
94 |
-
|
95 |
-
function view($params) {
|
96 |
-
$request_url = "{$this->url}&api_action=message_view&api_output={$this->output}&{$params}";
|
97 |
-
$response = $this->curl($request_url);
|
98 |
-
return $response;
|
99 |
-
}
|
100 |
-
|
101 |
-
}
|
102 |
-
|
103 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Organization.class.php
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Organization extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function list_($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=organization_list&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
}
|
24 |
-
|
25 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Segment.class.php
DELETED
@@ -1,26 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Segment extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function list_($params) {
|
18 |
-
// version 2 only
|
19 |
-
$request_url = "{$this->url_base}/segment/list";
|
20 |
-
$response = $this->curl($request_url, $params, "GET", "segment_list");
|
21 |
-
return $response;
|
22 |
-
}
|
23 |
-
|
24 |
-
}
|
25 |
-
|
26 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Settings.class.php
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Settings extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function edit($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=settings_edit&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
}
|
24 |
-
|
25 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Subscriber.class.php
DELETED
@@ -1,6 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Subscriber extends AC_Contact {
|
4 |
-
}
|
5 |
-
|
6 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Tag.class.php
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Tag extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function list_($params) {
|
18 |
-
$request_url = "{$this->url}&api_action=tags_list&api_output={$this->output}&{$params}";
|
19 |
-
$response = $this->curl($request_url);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
}
|
24 |
-
|
25 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Tracking.class.php
DELETED
@@ -1,118 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Tracking extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
/*
|
18 |
-
* Update the status (enabled or disabled) for site tracking.
|
19 |
-
*/
|
20 |
-
function site_status($params, $post_data) {
|
21 |
-
// version 2 only.
|
22 |
-
$request_url = "{$this->url_base}/track/site";
|
23 |
-
$response = $this->curl($request_url, $post_data, "POST", "tracking_site_status");
|
24 |
-
return $response;
|
25 |
-
}
|
26 |
-
|
27 |
-
/*
|
28 |
-
* Update the status (enabled or disabled) for event tracking.
|
29 |
-
*/
|
30 |
-
function event_status($params, $post_data) {
|
31 |
-
// version 2 only.
|
32 |
-
$request_url = "{$this->url_base}/track/event";
|
33 |
-
$response = $this->curl($request_url, $post_data, "POST", "tracking_event_status");
|
34 |
-
return $response;
|
35 |
-
}
|
36 |
-
|
37 |
-
/*
|
38 |
-
* Returns existing whitelisted domains.
|
39 |
-
*/
|
40 |
-
function site_list($params) {
|
41 |
-
if ($this->version == 1) {
|
42 |
-
// not supported currently.
|
43 |
-
//$request_url = "{$this->url}&api_action=contact_delete_list&api_output={$this->output}&{$params}";
|
44 |
-
} elseif ($this->version == 2) {
|
45 |
-
$request_url = "{$this->url_base}/track/site";
|
46 |
-
}
|
47 |
-
$response = $this->curl($request_url, array(), "GET", "tracking_site_list");
|
48 |
-
return $response;
|
49 |
-
}
|
50 |
-
|
51 |
-
/*
|
52 |
-
* Returns existing tracked events.
|
53 |
-
*/
|
54 |
-
function event_list($params) {
|
55 |
-
if ($this->version == 1) {
|
56 |
-
// not supported currently.
|
57 |
-
//$request_url = "{$this->url}&api_action=contact_delete_list&api_output={$this->output}&{$params}";
|
58 |
-
} elseif ($this->version == 2) {
|
59 |
-
$request_url = "{$this->url_base}/track/event";
|
60 |
-
}
|
61 |
-
$response = $this->curl($request_url, array(), "GET", "tracking_event_list");
|
62 |
-
return $response;
|
63 |
-
}
|
64 |
-
|
65 |
-
/*
|
66 |
-
* Adds a domain to the site tracking whitelist.
|
67 |
-
*/
|
68 |
-
function whitelist($params, $post_data) {
|
69 |
-
// version 2 only.
|
70 |
-
$request_url = "{$this->url_base}/track/site";
|
71 |
-
$response = $this->curl($request_url, $post_data, "PUT", "tracking_whitelist");
|
72 |
-
return $response;
|
73 |
-
}
|
74 |
-
|
75 |
-
/*
|
76 |
-
* Removes a domain from the site tracking whitelist.
|
77 |
-
*/
|
78 |
-
function whitelist_remove($params, $post_data) {
|
79 |
-
// version 2 only.
|
80 |
-
$request_url = "{$this->url_base}/track/site";
|
81 |
-
$response = $this->curl($request_url, $post_data, "DELETE", "tracking_whitelist");
|
82 |
-
return $response;
|
83 |
-
}
|
84 |
-
|
85 |
-
/*
|
86 |
-
* Removes an event.
|
87 |
-
*/
|
88 |
-
function event_remove($params, $post_data) {
|
89 |
-
// version 2 only.
|
90 |
-
$request_url = "{$this->url_base}/track/event";
|
91 |
-
$response = $this->curl($request_url, $post_data, "DELETE", "tracking_event_remove");
|
92 |
-
return $response;
|
93 |
-
}
|
94 |
-
|
95 |
-
/*
|
96 |
-
* Adds a new event.
|
97 |
-
*/
|
98 |
-
function log($params, $post_data) {
|
99 |
-
$request_url = "https://trackcmp.net/event";
|
100 |
-
$post_data["actid"] = $this->track_actid;
|
101 |
-
$post_data["key"] = $this->track_key;
|
102 |
-
$visit_data = array();
|
103 |
-
if ($this->track_email) {
|
104 |
-
$visit_data["email"] = $this->track_email;
|
105 |
-
}
|
106 |
-
if (isset($post_data["visit"])) {
|
107 |
-
$visit_data = array_merge($visit_data, $post_data["visit"]);
|
108 |
-
}
|
109 |
-
if ($visit_data) {
|
110 |
-
$post_data["visit"] = json_encode($visit_data);
|
111 |
-
}
|
112 |
-
$response = $this->curl($request_url, $post_data, "POST", "tracking_log");
|
113 |
-
return $response;
|
114 |
-
}
|
115 |
-
|
116 |
-
}
|
117 |
-
|
118 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/User.class.php
DELETED
@@ -1,71 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_User extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=user_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function delete_list($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=user_delete_list&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete($params) {
|
30 |
-
$request_url = "{$this->url}&api_action=user_delete&api_output={$this->output}&{$params}";
|
31 |
-
$response = $this->curl($request_url);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function edit($params, $post_data) {
|
36 |
-
$request_url = "{$this->url}&api_action=user_edit&api_output={$this->output}";
|
37 |
-
$response = $this->curl($request_url, $post_data);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function list_($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=user_list&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function me() {
|
48 |
-
$request_url = "{$this->url}&api_action=user_me&api_output={$this->output}";
|
49 |
-
$response = $this->curl($request_url);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function view($params) {
|
54 |
-
// can be a user ID, email, or username
|
55 |
-
if (preg_match("/^email=/", $params)) {
|
56 |
-
$action = "user_view_email";
|
57 |
-
}
|
58 |
-
elseif (preg_match("/^username=/", $params)) {
|
59 |
-
$action = "user_view_username";
|
60 |
-
}
|
61 |
-
elseif (preg_match("/^id=/", $params)) {
|
62 |
-
$action = "user_view";
|
63 |
-
}
|
64 |
-
$request_url = "{$this->url}&api_action={$action}&api_output={$this->output}&{$params}";
|
65 |
-
$response = $this->curl($request_url);
|
66 |
-
return $response;
|
67 |
-
}
|
68 |
-
|
69 |
-
}
|
70 |
-
|
71 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/Webhook.class.php
DELETED
@@ -1,84 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class AC_Webhook extends ActiveCampaign {
|
4 |
-
|
5 |
-
public $version;
|
6 |
-
public $url_base;
|
7 |
-
public $url;
|
8 |
-
public $api_key;
|
9 |
-
|
10 |
-
function __construct($version, $url_base, $url, $api_key) {
|
11 |
-
$this->version = $version;
|
12 |
-
$this->url_base = $url_base;
|
13 |
-
$this->url = $url;
|
14 |
-
$this->api_key = $api_key;
|
15 |
-
}
|
16 |
-
|
17 |
-
function add($params, $post_data) {
|
18 |
-
$request_url = "{$this->url}&api_action=webhook_add&api_output={$this->output}";
|
19 |
-
$response = $this->curl($request_url, $post_data);
|
20 |
-
return $response;
|
21 |
-
}
|
22 |
-
|
23 |
-
function delete($params) {
|
24 |
-
$request_url = "{$this->url}&api_action=webhook_delete&api_output={$this->output}&{$params}";
|
25 |
-
$response = $this->curl($request_url);
|
26 |
-
return $response;
|
27 |
-
}
|
28 |
-
|
29 |
-
function delete_list($params) {
|
30 |
-
$request_url = "{$this->url}&api_action=webhook_delete_list&api_output={$this->output}&{$params}";
|
31 |
-
$response = $this->curl($request_url);
|
32 |
-
return $response;
|
33 |
-
}
|
34 |
-
|
35 |
-
function edit($params, $post_data) {
|
36 |
-
$request_url = "{$this->url}&api_action=webhook_edit&api_output={$this->output}";
|
37 |
-
$response = $this->curl($request_url, $post_data);
|
38 |
-
return $response;
|
39 |
-
}
|
40 |
-
|
41 |
-
function list_($params) {
|
42 |
-
$request_url = "{$this->url}&api_action=webhook_list&api_output={$this->output}&{$params}";
|
43 |
-
$response = $this->curl($request_url);
|
44 |
-
return $response;
|
45 |
-
}
|
46 |
-
|
47 |
-
function view($params) {
|
48 |
-
$request_url = "{$this->url}&api_action=webhook_view&api_output={$this->output}&{$params}";
|
49 |
-
$response = $this->curl($request_url);
|
50 |
-
return $response;
|
51 |
-
}
|
52 |
-
|
53 |
-
function events($params) {
|
54 |
-
$request_url = "{$this->url}&api_action=webhook_events&api_output={$this->output}&{$params}";
|
55 |
-
$response = $this->curl($request_url);
|
56 |
-
return $response;
|
57 |
-
}
|
58 |
-
|
59 |
-
function process($params) {
|
60 |
-
// process an incoming webhook payload (from ActiveCampaign), and format it (or do something with it)
|
61 |
-
|
62 |
-
$r = array();
|
63 |
-
if ($_SERVER["REQUEST_METHOD"] != "POST") return $r;
|
64 |
-
|
65 |
-
$params_array = explode("&", $params);
|
66 |
-
$params_ = array();
|
67 |
-
foreach ($params_array as $expression) {
|
68 |
-
// IE: css=1
|
69 |
-
list($var, $val) = explode("=", $expression);
|
70 |
-
$params_[$var] = $val;
|
71 |
-
}
|
72 |
-
|
73 |
-
$event = $params_["event"];
|
74 |
-
$format = $params_["output"];
|
75 |
-
|
76 |
-
if ($format == "json") {
|
77 |
-
return json_encode($_POST);
|
78 |
-
}
|
79 |
-
|
80 |
-
}
|
81 |
-
|
82 |
-
}
|
83 |
-
|
84 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/config.php
DELETED
@@ -1,6 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
define("ACTIVECAMPAIGN_URL", "");
|
4 |
-
define("ACTIVECAMPAIGN_API_KEY", "");
|
5 |
-
|
6 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Integrations/activecampaign-api-php/includes/exceptions/RequestException.php
DELETED
@@ -1,25 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
class RequestException extends \Exception {
|
4 |
-
|
5 |
-
private $failedRequestMessage;
|
6 |
-
|
7 |
-
/**
|
8 |
-
* @param string message Response error message from the server.
|
9 |
-
*
|
10 |
-
* Set the failure message for this exception.
|
11 |
-
*/
|
12 |
-
public function setFailedMessage($message) {
|
13 |
-
$this->failedRequestMessage = $message;
|
14 |
-
$this->message = sprintf('An unexpected problem occurred with the API request. Some causes include: invalid JSON or XML returned. Here is the actual response from the server: ---- %s', $message);
|
15 |
-
}
|
16 |
-
|
17 |
-
/**
|
18 |
-
* @return string Response error message from the server.
|
19 |
-
*
|
20 |
-
* Get the failure message for this exception.
|
21 |
-
*/
|
22 |
-
public function getFailedMessage() {
|
23 |
-
return $this->failedRequestMessage;
|
24 |
-
}
|
25 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Slack.php
DELETED
@@ -1,119 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace FluentForm\App\Services;
|
4 |
-
|
5 |
-
use FluentForm\Framework\Helpers\ArrayHelper;
|
6 |
-
use FluentForm\App\Modules\Form\FormDataParser;
|
7 |
-
use FluentForm\App\Modules\Form\FormFieldsParser;
|
8 |
-
|
9 |
-
class Slack
|
10 |
-
{
|
11 |
-
/**
|
12 |
-
* The slack integration settings of the form.
|
13 |
-
*
|
14 |
-
* @var array $settings
|
15 |
-
*/
|
16 |
-
protected $settings = [];
|
17 |
-
|
18 |
-
/**
|
19 |
-
* Determine whether the slack notification should be sent or not.
|
20 |
-
*
|
21 |
-
* @param $formId
|
22 |
-
*
|
23 |
-
* @return boolean
|
24 |
-
*/
|
25 |
-
public function shouldApply($formId)
|
26 |
-
{
|
27 |
-
$this->settings = wpFluent()->table('fluentform_form_meta')
|
28 |
-
->where('form_id', $formId)
|
29 |
-
->where('meta_key', 'slack')
|
30 |
-
->first();
|
31 |
-
|
32 |
-
$this->settings = $this->settings ? json_decode($this->settings->value, true) : [];
|
33 |
-
|
34 |
-
return ArrayHelper::get($this->settings, 'enabled', false);
|
35 |
-
}
|
36 |
-
|
37 |
-
/**
|
38 |
-
* Handle slack notifier.
|
39 |
-
*
|
40 |
-
* @param $submissionId
|
41 |
-
* @param $formData
|
42 |
-
* @param $form
|
43 |
-
*/
|
44 |
-
public function handle($submissionId, $formData, $form)
|
45 |
-
{
|
46 |
-
if (!$this->shouldApply($form->id)) {
|
47 |
-
return;
|
48 |
-
}
|
49 |
-
|
50 |
-
$inputs = FormFieldsParser::getEntryInputs($form);
|
51 |
-
|
52 |
-
$labels = FormFieldsParser::getAdminLabels($form, $formFields);
|
53 |
-
|
54 |
-
$formData = FormDataParser::parseData((object) $formData, $inputs, $form->id);
|
55 |
-
|
56 |
-
$title = __("New submission on ".$form->title, 'fluentform');
|
57 |
-
|
58 |
-
$fields = [];
|
59 |
-
|
60 |
-
foreach ($formData as $attribute => $value) {
|
61 |
-
$value = str_replace('&', '&', $value);
|
62 |
-
$value = str_replace('<', '<', $value);
|
63 |
-
$value = str_replace('>', ">", $value);
|
64 |
-
|
65 |
-
$fields[] = [
|
66 |
-
'title' => $labels[$attribute],
|
67 |
-
'value' => $value,
|
68 |
-
'short' => false
|
69 |
-
];
|
70 |
-
}
|
71 |
-
|
72 |
-
$slackHook = ArrayHelper::get($this->settings, 'webhook');
|
73 |
-
|
74 |
-
$titleLink = admin_url('admin.php?page=fluent_forms&form_id='
|
75 |
-
.$form->id
|
76 |
-
.'&route=entries#/entries/'
|
77 |
-
.$submissionId
|
78 |
-
);
|
79 |
-
|
80 |
-
$body = [
|
81 |
-
'payload' => json_encode([
|
82 |
-
'attachments' => [
|
83 |
-
[
|
84 |
-
'color' => '#0078ff',
|
85 |
-
'fallback' => $title,
|
86 |
-
'title' => $title,
|
87 |
-
'title_link' => $titleLink,
|
88 |
-
'fields' => $fields,
|
89 |
-
'footer' => 'fluentform',
|
90 |
-
'ts' => current_time('timestamp')
|
91 |
-
]
|
92 |
-
]
|
93 |
-
])
|
94 |
-
];
|
95 |
-
|
96 |
-
wp_remote_post($slackHook, [
|
97 |
-
'method' => 'POST',
|
98 |
-
'timeout' => 30,
|
99 |
-
'redirection' => 5,
|
100 |
-
'httpversion' => '1.0',
|
101 |
-
'blocking' => false,
|
102 |
-
'headers' => [],
|
103 |
-
'body' => $body,
|
104 |
-
'cookies' => []
|
105 |
-
]);
|
106 |
-
}
|
107 |
-
|
108 |
-
/**
|
109 |
-
* Invoke slack notifier.
|
110 |
-
*
|
111 |
-
* @param $submissionId
|
112 |
-
* @param $formData
|
113 |
-
* @param $form
|
114 |
-
*/
|
115 |
-
public static function notify($submissionId, $formData, $form)
|
116 |
-
{
|
117 |
-
(new static)->handle($submissionId, $formData, $form);
|
118 |
-
}
|
119 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/Spout/Autoloader/Psr4Autoloader.php
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Autoloader;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class Psr4Autoloader
|
7 |
+
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md#class-example
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Autoloader
|
10 |
+
*/
|
11 |
+
class Psr4Autoloader
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* An associative array where the key is a namespace prefix and the value
|
15 |
+
* is an array of base directories for classes in that namespace.
|
16 |
+
*
|
17 |
+
* @var array
|
18 |
+
*/
|
19 |
+
protected $prefixes = array();
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Register loader with SPL autoloader stack.
|
23 |
+
*
|
24 |
+
* @return void
|
25 |
+
*/
|
26 |
+
public function register()
|
27 |
+
{
|
28 |
+
spl_autoload_register(array($this, 'loadClass'));
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Adds a base directory for a namespace prefix.
|
33 |
+
*
|
34 |
+
* @param string $prefix The namespace prefix.
|
35 |
+
* @param string $baseDir A base directory for class files in the
|
36 |
+
* namespace.
|
37 |
+
* @param bool $prepend If true, prepend the base directory to the stack
|
38 |
+
* instead of appending it; this causes it to be searched first rather
|
39 |
+
* than last.
|
40 |
+
* @return void
|
41 |
+
*/
|
42 |
+
public function addNamespace($prefix, $baseDir, $prepend = false)
|
43 |
+
{
|
44 |
+
// normalize namespace prefix
|
45 |
+
$prefix = trim($prefix, '\\') . '\\';
|
46 |
+
|
47 |
+
// normalize the base directory with a trailing separator
|
48 |
+
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
|
49 |
+
|
50 |
+
// initialize the namespace prefix array
|
51 |
+
if (isset($this->prefixes[$prefix]) === false) {
|
52 |
+
$this->prefixes[$prefix] = array();
|
53 |
+
}
|
54 |
+
|
55 |
+
// retain the base directory for the namespace prefix
|
56 |
+
if ($prepend) {
|
57 |
+
array_unshift($this->prefixes[$prefix], $baseDir);
|
58 |
+
} else {
|
59 |
+
array_push($this->prefixes[$prefix], $baseDir);
|
60 |
+
}
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Loads the class file for a given class name.
|
65 |
+
*
|
66 |
+
* @param string $class The fully-qualified class name.
|
67 |
+
* @return mixed The mapped file name on success, or boolean false on
|
68 |
+
* failure.
|
69 |
+
*/
|
70 |
+
public function loadClass($class)
|
71 |
+
{
|
72 |
+
// the current namespace prefix
|
73 |
+
$prefix = $class;
|
74 |
+
|
75 |
+
// work backwards through the namespace names of the fully-qualified
|
76 |
+
// class name to find a mapped file name
|
77 |
+
while (false !== $pos = strrpos($prefix, '\\')) {
|
78 |
+
|
79 |
+
// retain the trailing namespace separator in the prefix
|
80 |
+
$prefix = substr($class, 0, $pos + 1);
|
81 |
+
|
82 |
+
// the rest is the relative class name
|
83 |
+
$relativeClass = substr($class, $pos + 1);
|
84 |
+
|
85 |
+
// try to load a mapped file for the prefix and relative class
|
86 |
+
$mappedFile = $this->loadMappedFile($prefix, $relativeClass);
|
87 |
+
if ($mappedFile !== false) {
|
88 |
+
return $mappedFile;
|
89 |
+
}
|
90 |
+
|
91 |
+
// remove the trailing namespace separator for the next iteration
|
92 |
+
// of strrpos()
|
93 |
+
$prefix = rtrim($prefix, '\\');
|
94 |
+
}
|
95 |
+
|
96 |
+
// never found a mapped file
|
97 |
+
return false;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Load the mapped file for a namespace prefix and relative class.
|
102 |
+
*
|
103 |
+
* @param string $prefix The namespace prefix.
|
104 |
+
* @param string $relativeClass The relative class name.
|
105 |
+
* @return mixed Boolean false if no mapped file can be loaded, or the
|
106 |
+
* name of the mapped file that was loaded.
|
107 |
+
*/
|
108 |
+
protected function loadMappedFile($prefix, $relativeClass)
|
109 |
+
{
|
110 |
+
// are there any base directories for this namespace prefix?
|
111 |
+
if (isset($this->prefixes[$prefix]) === false) {
|
112 |
+
return false;
|
113 |
+
}
|
114 |
+
|
115 |
+
// look through base directories for this namespace prefix
|
116 |
+
foreach ($this->prefixes[$prefix] as $baseDir) {
|
117 |
+
|
118 |
+
// replace the namespace prefix with the base directory,
|
119 |
+
// replace namespace separators with directory separators
|
120 |
+
// in the relative class name, append with .php
|
121 |
+
$file = $baseDir
|
122 |
+
. str_replace('\\', '/', $relativeClass)
|
123 |
+
. '.php';
|
124 |
+
|
125 |
+
// if the mapped file exists, require it
|
126 |
+
if ($this->requireFile($file)) {
|
127 |
+
// yes, we're done
|
128 |
+
return $file;
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
// never found it
|
133 |
+
return false;
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* If a file exists, require it from the file system.
|
138 |
+
*
|
139 |
+
* @param string $file The file to require.
|
140 |
+
* @return bool True if the file exists, false if not.
|
141 |
+
*/
|
142 |
+
protected function requireFile($file)
|
143 |
+
{
|
144 |
+
if (file_exists($file)) {
|
145 |
+
require $file;
|
146 |
+
return true;
|
147 |
+
}
|
148 |
+
return false;
|
149 |
+
}
|
150 |
+
}
|
app/Services/Spout/Autoloader/autoload.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Autoloader;
|
4 |
+
|
5 |
+
require_once 'Psr4Autoloader.php';
|
6 |
+
|
7 |
+
/**
|
8 |
+
* @var string $srcBaseDirectory
|
9 |
+
* Full path to "src/Spout" which is what we want "Box\Spout" to map to.
|
10 |
+
*/
|
11 |
+
$srcBaseDirectory = dirname(dirname(__FILE__));
|
12 |
+
|
13 |
+
$loader = new Psr4Autoloader();
|
14 |
+
$loader->register();
|
15 |
+
$loader->addNamespace('Box\Spout', $srcBaseDirectory);
|
app/Services/Spout/Common/Escaper/CSV.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Escaper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class CSV
|
7 |
+
* Provides functions to escape and unescape data for CSV files
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Common\Escaper
|
10 |
+
*/
|
11 |
+
class CSV implements EscaperInterface
|
12 |
+
{
|
13 |
+
/**
|
14 |
+
* Escapes the given string to make it compatible with CSV
|
15 |
+
*
|
16 |
+
* @codeCoverageIgnore
|
17 |
+
*
|
18 |
+
* @param string $string The string to escape
|
19 |
+
* @return string The escaped string
|
20 |
+
*/
|
21 |
+
public function escape($string)
|
22 |
+
{
|
23 |
+
return $string;
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Unescapes the given string to make it compatible with CSV
|
28 |
+
*
|
29 |
+
* @codeCoverageIgnore
|
30 |
+
*
|
31 |
+
* @param string $string The string to unescape
|
32 |
+
* @return string The unescaped string
|
33 |
+
*/
|
34 |
+
public function unescape($string)
|
35 |
+
{
|
36 |
+
return $string;
|
37 |
+
}
|
38 |
+
}
|
app/Services/Spout/Common/Escaper/EscaperInterface.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Escaper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface EscaperInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Common\Escaper
|
9 |
+
*/
|
10 |
+
interface EscaperInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Escapes the given string to make it compatible with PHP
|
14 |
+
*
|
15 |
+
* @param string $string The string to escape
|
16 |
+
* @return string The escaped string
|
17 |
+
*/
|
18 |
+
public function escape($string);
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Unescapes the given string to make it compatible with PHP
|
22 |
+
*
|
23 |
+
* @param string $string The string to unescape
|
24 |
+
* @return string The unescaped string
|
25 |
+
*/
|
26 |
+
public function unescape($string);
|
27 |
+
}
|
app/Services/Spout/Common/Escaper/ODS.php
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Escaper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Singleton;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ODS
|
9 |
+
* Provides functions to escape and unescape data for ODS files
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Common\Escaper
|
12 |
+
*/
|
13 |
+
class ODS implements EscaperInterface
|
14 |
+
{
|
15 |
+
use Singleton;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Escapes the given string to make it compatible with XLSX
|
19 |
+
*
|
20 |
+
* @param string $string The string to escape
|
21 |
+
* @return string The escaped string
|
22 |
+
*/
|
23 |
+
public function escape($string)
|
24 |
+
{
|
25 |
+
if (defined('ENT_DISALLOWED')) {
|
26 |
+
// 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced.
|
27 |
+
// Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor
|
28 |
+
// @link https://github.com/box/spout/issues/329
|
29 |
+
$replacedString = htmlspecialchars($string, ENT_NOQUOTES | ENT_DISALLOWED);
|
30 |
+
} else {
|
31 |
+
// We are on hhvm or any other engine that does not support ENT_DISALLOWED.
|
32 |
+
//
|
33 |
+
// @NOTE: Using ENT_NOQUOTES as only XML entities ('<', '>', '&') need to be encoded.
|
34 |
+
// Single and double quotes can be left as is.
|
35 |
+
$escapedString = htmlspecialchars($string, ENT_NOQUOTES);
|
36 |
+
|
37 |
+
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
38 |
+
// some characters should not be escaped though: "\t", "\r" and "\n".
|
39 |
+
$regexPattern = '[\x00-\x08' .
|
40 |
+
// skipping "\t" (0x9) and "\n" (0xA)
|
41 |
+
'\x0B-\x0C' .
|
42 |
+
// skipping "\r" (0xD)
|
43 |
+
'\x0E-\x1F]';
|
44 |
+
$replacedString = preg_replace("/$regexPattern/", '�', $escapedString);
|
45 |
+
}
|
46 |
+
|
47 |
+
return $replacedString;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Unescapes the given string to make it compatible with XLSX
|
52 |
+
*
|
53 |
+
* @param string $string The string to unescape
|
54 |
+
* @return string The unescaped string
|
55 |
+
*/
|
56 |
+
public function unescape($string)
|
57 |
+
{
|
58 |
+
// ==============
|
59 |
+
// = WARNING =
|
60 |
+
// ==============
|
61 |
+
// It is assumed that the given string has already had its XML entities decoded.
|
62 |
+
// This is true if the string is coming from a DOMNode (as DOMNode already decode XML entities on creation).
|
63 |
+
// Therefore there is no need to call "htmlspecialchars_decode()".
|
64 |
+
return $string;
|
65 |
+
}
|
66 |
+
}
|
app/Services/Spout/Common/Escaper/XLSX.php
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Escaper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Singleton;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class XLSX
|
9 |
+
* Provides functions to escape and unescape data for XLSX files
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Common\Escaper
|
12 |
+
*/
|
13 |
+
class XLSX implements EscaperInterface
|
14 |
+
{
|
15 |
+
use Singleton;
|
16 |
+
|
17 |
+
/** @var string Regex pattern to detect control characters that need to be escaped */
|
18 |
+
protected $escapableControlCharactersPattern;
|
19 |
+
|
20 |
+
/** @var string[] Map containing control characters to be escaped (key) and their escaped value (value) */
|
21 |
+
protected $controlCharactersEscapingMap;
|
22 |
+
|
23 |
+
/** @var string[] Map containing control characters to be escaped (value) and their escaped value (key) */
|
24 |
+
protected $controlCharactersEscapingReverseMap;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Initializes the singleton instance
|
28 |
+
*/
|
29 |
+
protected function init()
|
30 |
+
{
|
31 |
+
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
|
32 |
+
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
|
33 |
+
$this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap);
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Escapes the given string to make it compatible with XLSX
|
38 |
+
*
|
39 |
+
* @param string $string The string to escape
|
40 |
+
* @return string The escaped string
|
41 |
+
*/
|
42 |
+
public function escape($string)
|
43 |
+
{
|
44 |
+
$escapedString = $this->escapeControlCharacters($string);
|
45 |
+
// @NOTE: Using ENT_NOQUOTES as only XML entities ('<', '>', '&') need to be encoded.
|
46 |
+
// Single and double quotes can be left as is.
|
47 |
+
$escapedString = htmlspecialchars($escapedString, ENT_NOQUOTES);
|
48 |
+
|
49 |
+
return $escapedString;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Unescapes the given string to make it compatible with XLSX
|
54 |
+
*
|
55 |
+
* @param string $string The string to unescape
|
56 |
+
* @return string The unescaped string
|
57 |
+
*/
|
58 |
+
public function unescape($string)
|
59 |
+
{
|
60 |
+
// ==============
|
61 |
+
// = WARNING =
|
62 |
+
// ==============
|
63 |
+
// It is assumed that the given string has already had its XML entities decoded.
|
64 |
+
// This is true if the string is coming from a DOMNode (as DOMNode already decode XML entities on creation).
|
65 |
+
// Therefore there is no need to call "htmlspecialchars_decode()".
|
66 |
+
$unescapedString = $this->unescapeControlCharacters($string);
|
67 |
+
|
68 |
+
return $unescapedString;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* @return string Regex pattern containing all escapable control characters
|
73 |
+
*/
|
74 |
+
protected function getEscapableControlCharactersPattern()
|
75 |
+
{
|
76 |
+
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
77 |
+
// some characters should not be escaped though: "\t", "\r" and "\n".
|
78 |
+
return '[\x00-\x08' .
|
79 |
+
// skipping "\t" (0x9) and "\n" (0xA)
|
80 |
+
'\x0B-\x0C' .
|
81 |
+
// skipping "\r" (0xD)
|
82 |
+
'\x0E-\x1F]';
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Builds the map containing control characters to be escaped
|
87 |
+
* mapped to their escaped values.
|
88 |
+
* "\t", "\r" and "\n" don't need to be escaped.
|
89 |
+
*
|
90 |
+
* NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
|
91 |
+
* @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
|
92 |
+
*
|
93 |
+
* @return string[]
|
94 |
+
*/
|
95 |
+
protected function getControlCharactersEscapingMap()
|
96 |
+
{
|
97 |
+
$controlCharactersEscapingMap = [];
|
98 |
+
|
99 |
+
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
100 |
+
for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) {
|
101 |
+
$character = chr($charValue);
|
102 |
+
if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
|
103 |
+
$charHexValue = dechex($charValue);
|
104 |
+
$escapedChar = '_x' . sprintf('%04s' , strtoupper($charHexValue)) . '_';
|
105 |
+
$controlCharactersEscapingMap[$escapedChar] = $character;
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
return $controlCharactersEscapingMap;
|
110 |
+
}
|
111 |
+
|
112 |
+
/**
|
113 |
+
* Converts PHP control characters from the given string to OpenXML escaped control characters
|
114 |
+
*
|
115 |
+
* Excel escapes control characters with _xHHHH_ and also escapes any
|
116 |
+
* literal strings of that type by encoding the leading underscore.
|
117 |
+
* So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_.
|
118 |
+
*
|
119 |
+
* NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
|
120 |
+
* @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
|
121 |
+
*
|
122 |
+
* @param string $string String to escape
|
123 |
+
* @return string
|
124 |
+
*/
|
125 |
+
protected function escapeControlCharacters($string)
|
126 |
+
{
|
127 |
+
$escapedString = $this->escapeEscapeCharacter($string);
|
128 |
+
|
129 |
+
// if no control characters
|
130 |
+
if (!preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) {
|
131 |
+
return $escapedString;
|
132 |
+
}
|
133 |
+
|
134 |
+
return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function($matches) {
|
135 |
+
return $this->controlCharactersEscapingReverseMap[$matches[0]];
|
136 |
+
}, $escapedString);
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* Escapes the escape character: "_x0000_" -> "_x005F_x0000_"
|
141 |
+
*
|
142 |
+
* @param string $string String to escape
|
143 |
+
* @return string The escaped string
|
144 |
+
*/
|
145 |
+
protected function escapeEscapeCharacter($string)
|
146 |
+
{
|
147 |
+
return preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Converts OpenXML escaped control characters from the given string to PHP control characters
|
152 |
+
*
|
153 |
+
* Excel escapes control characters with _xHHHH_ and also escapes any
|
154 |
+
* literal strings of that type by encoding the leading underscore.
|
155 |
+
* So "_x0000_" -> "\0" and "_x005F_x0000_" -> "_x0000_"
|
156 |
+
*
|
157 |
+
* NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
|
158 |
+
* @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
|
159 |
+
*
|
160 |
+
* @param string $string String to unescape
|
161 |
+
* @return string
|
162 |
+
*/
|
163 |
+
protected function unescapeControlCharacters($string)
|
164 |
+
{
|
165 |
+
$unescapedString = $string;
|
166 |
+
|
167 |
+
foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
|
168 |
+
// only unescape characters that don't contain the escaped escape character for now
|
169 |
+
$unescapedString = preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString);
|
170 |
+
}
|
171 |
+
|
172 |
+
return $this->unescapeEscapeCharacter($unescapedString);
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Unecapes the escape character: "_x005F_x0000_" => "_x0000_"
|
177 |
+
*
|
178 |
+
* @param string $string String to unescape
|
179 |
+
* @return string The unescaped string
|
180 |
+
*/
|
181 |
+
protected function unescapeEscapeCharacter($string)
|
182 |
+
{
|
183 |
+
return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
|
184 |
+
}
|
185 |
+
}
|
app/Services/Spout/Common/Exception/EncodingConversionException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class EncodingConversionException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Common\Exception
|
10 |
+
*/
|
11 |
+
class EncodingConversionException extends SpoutException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Common/Exception/IOException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class IOException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Common\Exception
|
10 |
+
*/
|
11 |
+
class IOException extends SpoutException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Common/Exception/InvalidArgumentException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class InvalidArgumentException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Common\Exception
|
10 |
+
*/
|
11 |
+
class InvalidArgumentException extends SpoutException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Common/Exception/SpoutException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class SpoutException
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Common\Exception
|
9 |
+
* @abstract
|
10 |
+
*/
|
11 |
+
abstract class SpoutException extends \Exception
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Common/Exception/UnsupportedTypeException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class UnsupportedTypeException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Common\Exception
|
10 |
+
*/
|
11 |
+
class UnsupportedTypeException extends SpoutException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Common/Helper/EncodingHelper.php
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\EncodingConversionException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class EncodingHelper
|
9 |
+
* This class provides helper functions to work with encodings.
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Common\Helper
|
12 |
+
*/
|
13 |
+
class EncodingHelper
|
14 |
+
{
|
15 |
+
/** Definition of the encodings that can have a BOM */
|
16 |
+
const ENCODING_UTF8 = 'UTF-8';
|
17 |
+
const ENCODING_UTF16_LE = 'UTF-16LE';
|
18 |
+
const ENCODING_UTF16_BE = 'UTF-16BE';
|
19 |
+
const ENCODING_UTF32_LE = 'UTF-32LE';
|
20 |
+
const ENCODING_UTF32_BE = 'UTF-32BE';
|
21 |
+
|
22 |
+
/** Definition of the BOMs for the different encodings */
|
23 |
+
const BOM_UTF8 = "\xEF\xBB\xBF";
|
24 |
+
const BOM_UTF16_LE = "\xFF\xFE";
|
25 |
+
const BOM_UTF16_BE = "\xFE\xFF";
|
26 |
+
const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
|
27 |
+
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
|
28 |
+
|
29 |
+
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
30 |
+
protected $globalFunctionsHelper;
|
31 |
+
|
32 |
+
/** @var array Map representing the encodings supporting BOMs (key) and their associated BOM (value) */
|
33 |
+
protected $supportedEncodingsWithBom;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
37 |
+
*/
|
38 |
+
public function __construct($globalFunctionsHelper)
|
39 |
+
{
|
40 |
+
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
41 |
+
|
42 |
+
$this->supportedEncodingsWithBom = [
|
43 |
+
self::ENCODING_UTF8 => self::BOM_UTF8,
|
44 |
+
self::ENCODING_UTF16_LE => self::BOM_UTF16_LE,
|
45 |
+
self::ENCODING_UTF16_BE => self::BOM_UTF16_BE,
|
46 |
+
self::ENCODING_UTF32_LE => self::BOM_UTF32_LE,
|
47 |
+
self::ENCODING_UTF32_BE => self::BOM_UTF32_BE,
|
48 |
+
];
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Returns the number of bytes to use as offset in order to skip the BOM.
|
53 |
+
*
|
54 |
+
* @param resource $filePointer Pointer to the file to check
|
55 |
+
* @param string $encoding Encoding of the file to check
|
56 |
+
* @return int Bytes offset to apply to skip the BOM (0 means no BOM)
|
57 |
+
*/
|
58 |
+
public function getBytesOffsetToSkipBOM($filePointer, $encoding)
|
59 |
+
{
|
60 |
+
$byteOffsetToSkipBom = 0;
|
61 |
+
|
62 |
+
if ($this->hasBOM($filePointer, $encoding)) {
|
63 |
+
$bomUsed = $this->supportedEncodingsWithBom[$encoding];
|
64 |
+
|
65 |
+
// we skip the N first bytes
|
66 |
+
$byteOffsetToSkipBom = strlen($bomUsed);
|
67 |
+
}
|
68 |
+
|
69 |
+
return $byteOffsetToSkipBom;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Returns whether the file identified by the given pointer has a BOM.
|
74 |
+
*
|
75 |
+
* @param resource $filePointer Pointer to the file to check
|
76 |
+
* @param string $encoding Encoding of the file to check
|
77 |
+
* @return bool TRUE if the file has a BOM, FALSE otherwise
|
78 |
+
*/
|
79 |
+
protected function hasBOM($filePointer, $encoding)
|
80 |
+
{
|
81 |
+
$hasBOM = false;
|
82 |
+
|
83 |
+
$this->globalFunctionsHelper->rewind($filePointer);
|
84 |
+
|
85 |
+
if (array_key_exists($encoding, $this->supportedEncodingsWithBom)) {
|
86 |
+
$potentialBom = $this->supportedEncodingsWithBom[$encoding];
|
87 |
+
$numBytesInBom = strlen($potentialBom);
|
88 |
+
|
89 |
+
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom);
|
90 |
+
}
|
91 |
+
|
92 |
+
return $hasBOM;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Attempts to convert a non UTF-8 string into UTF-8.
|
97 |
+
*
|
98 |
+
* @param string $string Non UTF-8 string to be converted
|
99 |
+
* @param string $sourceEncoding The encoding used to encode the source string
|
100 |
+
* @return string The converted, UTF-8 string
|
101 |
+
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
|
102 |
+
*/
|
103 |
+
public function attemptConversionToUTF8($string, $sourceEncoding)
|
104 |
+
{
|
105 |
+
return $this->attemptConversion($string, $sourceEncoding, self::ENCODING_UTF8);
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Attempts to convert a UTF-8 string into the given encoding.
|
110 |
+
*
|
111 |
+
* @param string $string UTF-8 string to be converted
|
112 |
+
* @param string $targetEncoding The encoding the string should be re-encoded into
|
113 |
+
* @return string The converted string, encoded with the given encoding
|
114 |
+
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
|
115 |
+
*/
|
116 |
+
public function attemptConversionFromUTF8($string, $targetEncoding)
|
117 |
+
{
|
118 |
+
return $this->attemptConversion($string, self::ENCODING_UTF8, $targetEncoding);
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Attempts to convert the given string to the given encoding.
|
123 |
+
* Depending on what is installed on the server, we will try to iconv or mbstring.
|
124 |
+
*
|
125 |
+
* @param string $string string to be converted
|
126 |
+
* @param string $sourceEncoding The encoding used to encode the source string
|
127 |
+
* @param string $targetEncoding The encoding the string should be re-encoded into
|
128 |
+
* @return string The converted string, encoded with the given encoding
|
129 |
+
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
|
130 |
+
*/
|
131 |
+
protected function attemptConversion($string, $sourceEncoding, $targetEncoding)
|
132 |
+
{
|
133 |
+
// if source and target encodings are the same, it's a no-op
|
134 |
+
if ($sourceEncoding === $targetEncoding) {
|
135 |
+
return $string;
|
136 |
+
}
|
137 |
+
|
138 |
+
$convertedString = null;
|
139 |
+
|
140 |
+
if ($this->canUseIconv()) {
|
141 |
+
$convertedString = $this->globalFunctionsHelper->iconv($string, $sourceEncoding, $targetEncoding);
|
142 |
+
} else if ($this->canUseMbString()) {
|
143 |
+
$convertedString = $this->globalFunctionsHelper->mb_convert_encoding($string, $sourceEncoding, $targetEncoding);
|
144 |
+
} else {
|
145 |
+
throw new EncodingConversionException("The conversion from $sourceEncoding to $targetEncoding is not supported. Please install \"iconv\" or \"PHP Intl\".");
|
146 |
+
}
|
147 |
+
|
148 |
+
if ($convertedString === false) {
|
149 |
+
throw new EncodingConversionException("The conversion from $sourceEncoding to $targetEncoding failed.");
|
150 |
+
}
|
151 |
+
|
152 |
+
return $convertedString;
|
153 |
+
}
|
154 |
+
|
155 |
+
/**
|
156 |
+
* Returns whether "iconv" can be used.
|
157 |
+
*
|
158 |
+
* @return bool TRUE if "iconv" is available and can be used, FALSE otherwise
|
159 |
+
*/
|
160 |
+
protected function canUseIconv()
|
161 |
+
{
|
162 |
+
return $this->globalFunctionsHelper->function_exists('iconv');
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Returns whether "mb_string" functions can be used.
|
167 |
+
* These functions come with the PHP Intl package.
|
168 |
+
*
|
169 |
+
* @return bool TRUE if "mb_string" functions are available and can be used, FALSE otherwise
|
170 |
+
*/
|
171 |
+
protected function canUseMbString()
|
172 |
+
{
|
173 |
+
return $this->globalFunctionsHelper->function_exists('mb_convert_encoding');
|
174 |
+
}
|
175 |
+
}
|
app/Services/Spout/Common/Helper/FileSystemHelper.php
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class FileSystemHelper
|
9 |
+
* This class provides helper functions to help with the file system operations
|
10 |
+
* like files/folders creation & deletion
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Common\Helper
|
13 |
+
*/
|
14 |
+
class FileSystemHelper
|
15 |
+
{
|
16 |
+
/** @var string Real path of the base folder where all the I/O can occur */
|
17 |
+
protected $baseFolderRealPath;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
|
21 |
+
*/
|
22 |
+
public function __construct($baseFolderPath)
|
23 |
+
{
|
24 |
+
$this->baseFolderRealPath = realpath($baseFolderPath);
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Creates an empty folder with the given name under the given parent folder.
|
29 |
+
*
|
30 |
+
* @param string $parentFolderPath The parent folder path under which the folder is going to be created
|
31 |
+
* @param string $folderName The name of the folder to create
|
32 |
+
* @return string Path of the created folder
|
33 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
|
34 |
+
*/
|
35 |
+
public function createFolder($parentFolderPath, $folderName)
|
36 |
+
{
|
37 |
+
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
|
38 |
+
|
39 |
+
$folderPath = $parentFolderPath . '/' . $folderName;
|
40 |
+
|
41 |
+
$wasCreationSuccessful = mkdir($folderPath, 0777, true);
|
42 |
+
if (!$wasCreationSuccessful) {
|
43 |
+
throw new IOException("Unable to create folder: $folderPath");
|
44 |
+
}
|
45 |
+
|
46 |
+
return $folderPath;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Creates a file with the given name and content in the given folder.
|
51 |
+
* The parent folder must exist.
|
52 |
+
*
|
53 |
+
* @param string $parentFolderPath The parent folder path where the file is going to be created
|
54 |
+
* @param string $fileName The name of the file to create
|
55 |
+
* @param string $fileContents The contents of the file to create
|
56 |
+
* @return string Path of the created file
|
57 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
|
58 |
+
*/
|
59 |
+
public function createFileWithContents($parentFolderPath, $fileName, $fileContents)
|
60 |
+
{
|
61 |
+
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
|
62 |
+
|
63 |
+
$filePath = $parentFolderPath . '/' . $fileName;
|
64 |
+
|
65 |
+
$wasCreationSuccessful = file_put_contents($filePath, $fileContents);
|
66 |
+
if ($wasCreationSuccessful === false) {
|
67 |
+
throw new IOException("Unable to create file: $filePath");
|
68 |
+
}
|
69 |
+
|
70 |
+
return $filePath;
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Delete the file at the given path
|
75 |
+
*
|
76 |
+
* @param string $filePath Path of the file to delete
|
77 |
+
* @return void
|
78 |
+
* @throws \Box\Spout\Common\Exception\IOException If the file path is not inside of the base folder
|
79 |
+
*/
|
80 |
+
public function deleteFile($filePath)
|
81 |
+
{
|
82 |
+
$this->throwIfOperationNotInBaseFolder($filePath);
|
83 |
+
|
84 |
+
if (file_exists($filePath) && is_file($filePath)) {
|
85 |
+
unlink($filePath);
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Delete the folder at the given path as well as all its contents
|
91 |
+
*
|
92 |
+
* @param string $folderPath Path of the folder to delete
|
93 |
+
* @return void
|
94 |
+
* @throws \Box\Spout\Common\Exception\IOException If the folder path is not inside of the base folder
|
95 |
+
*/
|
96 |
+
public function deleteFolderRecursively($folderPath)
|
97 |
+
{
|
98 |
+
$this->throwIfOperationNotInBaseFolder($folderPath);
|
99 |
+
|
100 |
+
$itemIterator = new \RecursiveIteratorIterator(
|
101 |
+
new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS),
|
102 |
+
\RecursiveIteratorIterator::CHILD_FIRST
|
103 |
+
);
|
104 |
+
|
105 |
+
foreach ($itemIterator as $item) {
|
106 |
+
if ($item->isDir()) {
|
107 |
+
rmdir($item->getPathname());
|
108 |
+
} else {
|
109 |
+
unlink($item->getPathname());
|
110 |
+
}
|
111 |
+
}
|
112 |
+
|
113 |
+
rmdir($folderPath);
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* All I/O operations must occur inside the base folder, for security reasons.
|
118 |
+
* This function will throw an exception if the folder where the I/O operation
|
119 |
+
* should occur is not inside the base folder.
|
120 |
+
*
|
121 |
+
* @param string $operationFolderPath The path of the folder where the I/O operation should occur
|
122 |
+
* @return void
|
123 |
+
* @throws \Box\Spout\Common\Exception\IOException If the folder where the I/O operation should occur is not inside the base folder
|
124 |
+
*/
|
125 |
+
protected function throwIfOperationNotInBaseFolder($operationFolderPath)
|
126 |
+
{
|
127 |
+
$operationFolderRealPath = realpath($operationFolderPath);
|
128 |
+
$isInBaseFolder = (strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
|
129 |
+
if (!$isInBaseFolder) {
|
130 |
+
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
|
131 |
+
}
|
132 |
+
}
|
133 |
+
}
|
app/Services/Spout/Common/Helper/GlobalFunctionsHelper.php
ADDED
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class GlobalFunctionsHelper
|
7 |
+
* This class wraps global functions to facilitate testing
|
8 |
+
*
|
9 |
+
* @codeCoverageIgnore
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Common\Helper
|
12 |
+
*/
|
13 |
+
class GlobalFunctionsHelper
|
14 |
+
{
|
15 |
+
/**
|
16 |
+
* Wrapper around global function fopen()
|
17 |
+
* @see fopen()
|
18 |
+
*
|
19 |
+
* @param string $fileName
|
20 |
+
* @param string $mode
|
21 |
+
* @return resource|bool
|
22 |
+
*/
|
23 |
+
public function fopen($fileName, $mode)
|
24 |
+
{
|
25 |
+
return fopen($fileName, $mode);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Wrapper around global function fgets()
|
30 |
+
* @see fgets()
|
31 |
+
*
|
32 |
+
* @param resource $handle
|
33 |
+
* @param int|void $length
|
34 |
+
* @return string
|
35 |
+
*/
|
36 |
+
public function fgets($handle, $length = null)
|
37 |
+
{
|
38 |
+
return fgets($handle, $length);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Wrapper around global function fputs()
|
43 |
+
* @see fputs()
|
44 |
+
*
|
45 |
+
* @param resource $handle
|
46 |
+
* @param string $string
|
47 |
+
* @return int
|
48 |
+
*/
|
49 |
+
public function fputs($handle, $string)
|
50 |
+
{
|
51 |
+
return fputs($handle, $string);
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Wrapper around global function fflush()
|
56 |
+
* @see fflush()
|
57 |
+
*
|
58 |
+
* @param resource $handle
|
59 |
+
* @return bool
|
60 |
+
*/
|
61 |
+
public function fflush($handle)
|
62 |
+
{
|
63 |
+
return fflush($handle);
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Wrapper around global function fseek()
|
68 |
+
* @see fseek()
|
69 |
+
*
|
70 |
+
* @param resource $handle
|
71 |
+
* @param int $offset
|
72 |
+
* @return int
|
73 |
+
*/
|
74 |
+
public function fseek($handle, $offset)
|
75 |
+
{
|
76 |
+
return fseek($handle, $offset);
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Wrapper around global function fgetcsv()
|
81 |
+
* @see fgetcsv()
|
82 |
+
*
|
83 |
+
* @param resource $handle
|
84 |
+
* @param int|void $length
|
85 |
+
* @param string|void $delimiter
|
86 |
+
* @param string|void $enclosure
|
87 |
+
* @return array
|
88 |
+
*/
|
89 |
+
public function fgetcsv($handle, $length = null, $delimiter = null, $enclosure = null)
|
90 |
+
{
|
91 |
+
return fgetcsv($handle, $length, $delimiter, $enclosure);
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Wrapper around global function fputcsv()
|
96 |
+
* @see fputcsv()
|
97 |
+
*
|
98 |
+
* @param resource $handle
|
99 |
+
* @param array $fields
|
100 |
+
* @param string|void $delimiter
|
101 |
+
* @param string|void $enclosure
|
102 |
+
* @return int
|
103 |
+
*/
|
104 |
+
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
|
105 |
+
{
|
106 |
+
return fputcsv($handle, $fields, $delimiter, $enclosure);
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Wrapper around global function fwrite()
|
111 |
+
* @see fwrite()
|
112 |
+
*
|
113 |
+
* @param resource $handle
|
114 |
+
* @param string $string
|
115 |
+
* @return int
|
116 |
+
*/
|
117 |
+
public function fwrite($handle, $string)
|
118 |
+
{
|
119 |
+
return fwrite($handle, $string);
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Wrapper around global function fclose()
|
124 |
+
* @see fclose()
|
125 |
+
*
|
126 |
+
* @param resource $handle
|
127 |
+
* @return bool
|
128 |
+
*/
|
129 |
+
public function fclose($handle)
|
130 |
+
{
|
131 |
+
return fclose($handle);
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Wrapper around global function rewind()
|
136 |
+
* @see rewind()
|
137 |
+
*
|
138 |
+
* @param resource $handle
|
139 |
+
* @return bool
|
140 |
+
*/
|
141 |
+
public function rewind($handle)
|
142 |
+
{
|
143 |
+
return rewind($handle);
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Wrapper around global function file_exists()
|
148 |
+
* @see file_exists()
|
149 |
+
*
|
150 |
+
* @param string $fileName
|
151 |
+
* @return bool
|
152 |
+
*/
|
153 |
+
public function file_exists($fileName)
|
154 |
+
{
|
155 |
+
return file_exists($fileName);
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Wrapper around global function file_get_contents()
|
160 |
+
* @see file_get_contents()
|
161 |
+
*
|
162 |
+
* @param string $filePath
|
163 |
+
* @return string
|
164 |
+
*/
|
165 |
+
public function file_get_contents($filePath)
|
166 |
+
{
|
167 |
+
$realFilePath = $this->convertToUseRealPath($filePath);
|
168 |
+
return file_get_contents($realFilePath);
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Updates the given file path to use a real path.
|
173 |
+
* This is to avoid issues on some Windows setup.
|
174 |
+
*
|
175 |
+
* @param string $filePath File path
|
176 |
+
* @return string The file path using a real path
|
177 |
+
*/
|
178 |
+
protected function convertToUseRealPath($filePath)
|
179 |
+
{
|
180 |
+
$realFilePath = $filePath;
|
181 |
+
|
182 |
+
if ($this->isZipStream($filePath)) {
|
183 |
+
if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
|
184 |
+
$documentPath = $matches[1];
|
185 |
+
$documentInsideZipPath = $matches[2];
|
186 |
+
$realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath;
|
187 |
+
}
|
188 |
+
} else {
|
189 |
+
$realFilePath = realpath($filePath);
|
190 |
+
}
|
191 |
+
|
192 |
+
return $realFilePath;
|
193 |
+
}
|
194 |
+
|
195 |
+
/**
|
196 |
+
* Returns whether the given path is a zip stream.
|
197 |
+
*
|
198 |
+
* @param string $path Path pointing to a document
|
199 |
+
* @return bool TRUE if path is a zip stream, FALSE otherwise
|
200 |
+
*/
|
201 |
+
protected function isZipStream($path)
|
202 |
+
{
|
203 |
+
return (strpos($path, 'zip://') === 0);
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Wrapper around global function feof()
|
208 |
+
* @see feof()
|
209 |
+
*
|
210 |
+
* @param resource
|
211 |
+
* @return bool
|
212 |
+
*/
|
213 |
+
public function feof($handle)
|
214 |
+
{
|
215 |
+
return feof($handle);
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Wrapper around global function is_readable()
|
220 |
+
* @see is_readable()
|
221 |
+
*
|
222 |
+
* @param string $fileName
|
223 |
+
* @return bool
|
224 |
+
*/
|
225 |
+
public function is_readable($fileName)
|
226 |
+
{
|
227 |
+
return is_readable($fileName);
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Wrapper around global function basename()
|
232 |
+
* @see basename()
|
233 |
+
*
|
234 |
+
* @param string $path
|
235 |
+
* @param string|void $suffix
|
236 |
+
* @return string
|
237 |
+
*/
|
238 |
+
public function basename($path, $suffix = null)
|
239 |
+
{
|
240 |
+
return basename($path, $suffix);
|
241 |
+
}
|
242 |
+
|
243 |
+
/**
|
244 |
+
* Wrapper around global function header()
|
245 |
+
* @see header()
|
246 |
+
*
|
247 |
+
* @param string $string
|
248 |
+
* @return void
|
249 |
+
*/
|
250 |
+
public function header($string)
|
251 |
+
{
|
252 |
+
header($string);
|
253 |
+
}
|
254 |
+
|
255 |
+
/**
|
256 |
+
* Wrapper around global function ob_end_clean()
|
257 |
+
* @see ob_end_clean()
|
258 |
+
*
|
259 |
+
* @return void
|
260 |
+
*/
|
261 |
+
public function ob_end_clean()
|
262 |
+
{
|
263 |
+
if (ob_get_length() > 0) {
|
264 |
+
ob_end_clean();
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
/**
|
269 |
+
* Wrapper around global function iconv()
|
270 |
+
* @see iconv()
|
271 |
+
*
|
272 |
+
* @param string $string The string to be converted
|
273 |
+
* @param string $sourceEncoding The encoding of the source string
|
274 |
+
* @param string $targetEncoding The encoding the source string should be converted to
|
275 |
+
* @return string|bool the converted string or FALSE on failure.
|
276 |
+
*/
|
277 |
+
public function iconv($string, $sourceEncoding, $targetEncoding)
|
278 |
+
{
|
279 |
+
return iconv($sourceEncoding, $targetEncoding, $string);
|
280 |
+
}
|
281 |
+
|
282 |
+
/**
|
283 |
+
* Wrapper around global function mb_convert_encoding()
|
284 |
+
* @see mb_convert_encoding()
|
285 |
+
*
|
286 |
+
* @param string $string The string to be converted
|
287 |
+
* @param string $sourceEncoding The encoding of the source string
|
288 |
+
* @param string $targetEncoding The encoding the source string should be converted to
|
289 |
+
* @return string|bool the converted string or FALSE on failure.
|
290 |
+
*/
|
291 |
+
public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding)
|
292 |
+
{
|
293 |
+
return mb_convert_encoding($string, $targetEncoding, $sourceEncoding);
|
294 |
+
}
|
295 |
+
|
296 |
+
/**
|
297 |
+
* Wrapper around global function stream_get_wrappers()
|
298 |
+
* @see stream_get_wrappers()
|
299 |
+
*
|
300 |
+
* @return array
|
301 |
+
*/
|
302 |
+
public function stream_get_wrappers()
|
303 |
+
{
|
304 |
+
return stream_get_wrappers();
|
305 |
+
}
|
306 |
+
|
307 |
+
/**
|
308 |
+
* Wrapper around global function function_exists()
|
309 |
+
* @see function_exists()
|
310 |
+
*
|
311 |
+
* @param string $functionName
|
312 |
+
* @return bool
|
313 |
+
*/
|
314 |
+
public function function_exists($functionName)
|
315 |
+
{
|
316 |
+
return function_exists($functionName);
|
317 |
+
}
|
318 |
+
}
|
app/Services/Spout/Common/Helper/StringHelper.php
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class StringHelper
|
7 |
+
* This class provides helper functions to work with strings and multibyte strings.
|
8 |
+
*
|
9 |
+
* @codeCoverageIgnore
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Common\Helper
|
12 |
+
*/
|
13 |
+
class StringHelper
|
14 |
+
{
|
15 |
+
/** @var bool Whether the mbstring extension is loaded */
|
16 |
+
protected $hasMbstringSupport;
|
17 |
+
|
18 |
+
/**
|
19 |
+
*
|
20 |
+
*/
|
21 |
+
public function __construct()
|
22 |
+
{
|
23 |
+
$this->hasMbstringSupport = extension_loaded('mbstring');
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Returns the length of the given string.
|
28 |
+
* It uses the multi-bytes function is available.
|
29 |
+
* @see strlen
|
30 |
+
* @see mb_strlen
|
31 |
+
*
|
32 |
+
* @param string $string
|
33 |
+
* @return int
|
34 |
+
*/
|
35 |
+
public function getStringLength($string)
|
36 |
+
{
|
37 |
+
return $this->hasMbstringSupport ? mb_strlen($string) : strlen($string);
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Returns the position of the first occurrence of the given character/substring within the given string.
|
42 |
+
* It uses the multi-bytes function is available.
|
43 |
+
* @see strpos
|
44 |
+
* @see mb_strpos
|
45 |
+
*
|
46 |
+
* @param string $char Needle
|
47 |
+
* @param string $string Haystack
|
48 |
+
* @return int Char/substring's first occurrence position within the string if found (starts at 0) or -1 if not found
|
49 |
+
*/
|
50 |
+
public function getCharFirstOccurrencePosition($char, $string)
|
51 |
+
{
|
52 |
+
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char);
|
53 |
+
return ($position !== false) ? $position : -1;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Returns the position of the last occurrence of the given character/substring within the given string.
|
58 |
+
* It uses the multi-bytes function is available.
|
59 |
+
* @see strrpos
|
60 |
+
* @see mb_strrpos
|
61 |
+
*
|
62 |
+
* @param string $char Needle
|
63 |
+
* @param string $string Haystack
|
64 |
+
* @return int Char/substring's last occurrence position within the string if found (starts at 0) or -1 if not found
|
65 |
+
*/
|
66 |
+
public function getCharLastOccurrencePosition($char, $string)
|
67 |
+
{
|
68 |
+
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char);
|
69 |
+
return ($position !== false) ? $position : -1;
|
70 |
+
}
|
71 |
+
}
|
app/Services/Spout/Common/Singleton.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class Singleton
|
7 |
+
* Defines a class as a singleton.
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Common
|
10 |
+
*/
|
11 |
+
trait Singleton
|
12 |
+
{
|
13 |
+
protected static $instance;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @return static
|
17 |
+
*/
|
18 |
+
final public static function getInstance()
|
19 |
+
{
|
20 |
+
return isset(static::$instance)
|
21 |
+
? static::$instance
|
22 |
+
: static::$instance = new static;
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Singleton constructor.
|
27 |
+
*/
|
28 |
+
final private function __construct()
|
29 |
+
{
|
30 |
+
$this->init();
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Initializes the singleton
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
protected function init() {}
|
38 |
+
|
39 |
+
final private function __wakeup() {}
|
40 |
+
final private function __clone() {}
|
41 |
+
}
|
app/Services/Spout/Common/Type.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Common;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class Type
|
7 |
+
* This class references the supported types
|
8 |
+
*
|
9 |
+
* @api
|
10 |
+
*/
|
11 |
+
abstract class Type
|
12 |
+
{
|
13 |
+
const CSV = 'csv';
|
14 |
+
const XLSX = 'xlsx';
|
15 |
+
const ODS = 'ods';
|
16 |
+
}
|
app/Services/Spout/Reader/AbstractReader.php
ADDED
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\Exception\ReaderNotOpenedException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class AbstractReader
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader
|
12 |
+
* @abstract
|
13 |
+
*/
|
14 |
+
abstract class AbstractReader implements ReaderInterface
|
15 |
+
{
|
16 |
+
/** @var bool Indicates whether the stream is currently open */
|
17 |
+
protected $isStreamOpened = false;
|
18 |
+
|
19 |
+
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
20 |
+
protected $globalFunctionsHelper;
|
21 |
+
|
22 |
+
/** @var \Box\Spout\Reader\Common\ReaderOptions Reader's customized options */
|
23 |
+
protected $options;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Returns the reader's current options
|
27 |
+
*
|
28 |
+
* @return \Box\Spout\Reader\Common\ReaderOptions
|
29 |
+
*/
|
30 |
+
abstract protected function getOptions();
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Returns whether stream wrappers are supported
|
34 |
+
*
|
35 |
+
* @return bool
|
36 |
+
*/
|
37 |
+
abstract protected function doesSupportStreamWrapper();
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Opens the file at the given file path to make it ready to be read
|
41 |
+
*
|
42 |
+
* @param string $filePath Path of the file to be read
|
43 |
+
* @return void
|
44 |
+
*/
|
45 |
+
abstract protected function openReader($filePath);
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Returns an iterator to iterate over sheets.
|
49 |
+
*
|
50 |
+
* @return \Iterator To iterate over sheets
|
51 |
+
*/
|
52 |
+
abstract protected function getConcreteSheetIterator();
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Closes the reader. To be used after reading the file.
|
56 |
+
*
|
57 |
+
* @return AbstractReader
|
58 |
+
*/
|
59 |
+
abstract protected function closeReader();
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
63 |
+
* @return AbstractReader
|
64 |
+
*/
|
65 |
+
public function setGlobalFunctionsHelper($globalFunctionsHelper)
|
66 |
+
{
|
67 |
+
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
68 |
+
return $this;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Sets whether date/time values should be returned as PHP objects or be formatted as strings.
|
73 |
+
*
|
74 |
+
* @api
|
75 |
+
* @param bool $shouldFormatDates
|
76 |
+
* @return AbstractReader
|
77 |
+
*/
|
78 |
+
public function setShouldFormatDates($shouldFormatDates)
|
79 |
+
{
|
80 |
+
$this->getOptions()->setShouldFormatDates($shouldFormatDates);
|
81 |
+
return $this;
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Sets whether empty rows should be returned or skipped.
|
86 |
+
*
|
87 |
+
* @api
|
88 |
+
* @param bool $shouldPreserveEmptyRows
|
89 |
+
* @return AbstractReader
|
90 |
+
*/
|
91 |
+
public function setShouldPreserveEmptyRows($shouldPreserveEmptyRows)
|
92 |
+
{
|
93 |
+
$this->getOptions()->setShouldPreserveEmptyRows($shouldPreserveEmptyRows);
|
94 |
+
return $this;
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Prepares the reader to read the given file. It also makes sure
|
99 |
+
* that the file exists and is readable.
|
100 |
+
*
|
101 |
+
* @api
|
102 |
+
* @param string $filePath Path of the file to be read
|
103 |
+
* @return void
|
104 |
+
* @throws \Box\Spout\Common\Exception\IOException If the file at the given path does not exist, is not readable or is corrupted
|
105 |
+
*/
|
106 |
+
public function open($filePath)
|
107 |
+
{
|
108 |
+
if ($this->isStreamWrapper($filePath) && (!$this->doesSupportStreamWrapper() || !$this->isSupportedStreamWrapper($filePath))) {
|
109 |
+
throw new IOException("Could not open $filePath for reading! Stream wrapper used is not supported for this type of file.");
|
110 |
+
}
|
111 |
+
|
112 |
+
if (!$this->isPhpStream($filePath)) {
|
113 |
+
// we skip the checks if the provided file path points to a PHP stream
|
114 |
+
if (!$this->globalFunctionsHelper->file_exists($filePath)) {
|
115 |
+
throw new IOException("Could not open $filePath for reading! File does not exist.");
|
116 |
+
} else if (!$this->globalFunctionsHelper->is_readable($filePath)) {
|
117 |
+
throw new IOException("Could not open $filePath for reading! File is not readable.");
|
118 |
+
}
|
119 |
+
}
|
120 |
+
|
121 |
+
try {
|
122 |
+
$fileRealPath = $this->getFileRealPath($filePath);
|
123 |
+
$this->openReader($fileRealPath);
|
124 |
+
$this->isStreamOpened = true;
|
125 |
+
} catch (\Exception $exception) {
|
126 |
+
throw new IOException("Could not open $filePath for reading! ({$exception->getMessage()})");
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Returns the real path of the given path.
|
132 |
+
* If the given path is a valid stream wrapper, returns the path unchanged.
|
133 |
+
*
|
134 |
+
* @param string $filePath
|
135 |
+
* @return string
|
136 |
+
*/
|
137 |
+
protected function getFileRealPath($filePath)
|
138 |
+
{
|
139 |
+
if ($this->isSupportedStreamWrapper($filePath)) {
|
140 |
+
return $filePath;
|
141 |
+
}
|
142 |
+
|
143 |
+
// Need to use realpath to fix "Can't open file" on some Windows setup
|
144 |
+
return realpath($filePath);
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Returns the scheme of the custom stream wrapper, if the path indicates a stream wrapper is used.
|
149 |
+
* For example, php://temp => php, s3://path/to/file => s3...
|
150 |
+
*
|
151 |
+
* @param string $filePath Path of the file to be read
|
152 |
+
* @return string|null The stream wrapper scheme or NULL if not a stream wrapper
|
153 |
+
*/
|
154 |
+
protected function getStreamWrapperScheme($filePath)
|
155 |
+
{
|
156 |
+
$streamScheme = null;
|
157 |
+
if (preg_match('/^(\w+):\/\//', $filePath, $matches)) {
|
158 |
+
$streamScheme = $matches[1];
|
159 |
+
}
|
160 |
+
return $streamScheme;
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Checks if the given path is an unsupported stream wrapper
|
165 |
+
* (like local path, php://temp, mystream://foo/bar...).
|
166 |
+
*
|
167 |
+
* @param string $filePath Path of the file to be read
|
168 |
+
* @return bool Whether the given path is an unsupported stream wrapper
|
169 |
+
*/
|
170 |
+
protected function isStreamWrapper($filePath)
|
171 |
+
{
|
172 |
+
return ($this->getStreamWrapperScheme($filePath) !== null);
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Checks if the given path is an supported stream wrapper
|
177 |
+
* (like php://temp, mystream://foo/bar...).
|
178 |
+
* If the given path is a local path, returns true.
|
179 |
+
*
|
180 |
+
* @param string $filePath Path of the file to be read
|
181 |
+
* @return bool Whether the given path is an supported stream wrapper
|
182 |
+
*/
|
183 |
+
protected function isSupportedStreamWrapper($filePath)
|
184 |
+
{
|
185 |
+
$streamScheme = $this->getStreamWrapperScheme($filePath);
|
186 |
+
return ($streamScheme !== null) ?
|
187 |
+
in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) :
|
188 |
+
true;
|
189 |
+
}
|
190 |
+
|
191 |
+
/**
|
192 |
+
* Checks if a path is a PHP stream (like php://output, php://memory, ...)
|
193 |
+
*
|
194 |
+
* @param string $filePath Path of the file to be read
|
195 |
+
* @return bool Whether the given path maps to a PHP stream
|
196 |
+
*/
|
197 |
+
protected function isPhpStream($filePath)
|
198 |
+
{
|
199 |
+
$streamScheme = $this->getStreamWrapperScheme($filePath);
|
200 |
+
return ($streamScheme === 'php');
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Returns an iterator to iterate over sheets.
|
205 |
+
*
|
206 |
+
* @api
|
207 |
+
* @return \Iterator To iterate over sheets
|
208 |
+
* @throws \Box\Spout\Reader\Exception\ReaderNotOpenedException If called before opening the reader
|
209 |
+
*/
|
210 |
+
public function getSheetIterator()
|
211 |
+
{
|
212 |
+
if (!$this->isStreamOpened) {
|
213 |
+
throw new ReaderNotOpenedException('Reader should be opened first.');
|
214 |
+
}
|
215 |
+
|
216 |
+
return $this->getConcreteSheetIterator();
|
217 |
+
}
|
218 |
+
|
219 |
+
/**
|
220 |
+
* Closes the reader, preventing any additional reading
|
221 |
+
*
|
222 |
+
* @api
|
223 |
+
* @return void
|
224 |
+
*/
|
225 |
+
public function close()
|
226 |
+
{
|
227 |
+
if ($this->isStreamOpened) {
|
228 |
+
$this->closeReader();
|
229 |
+
|
230 |
+
$sheetIterator = $this->getConcreteSheetIterator();
|
231 |
+
if ($sheetIterator) {
|
232 |
+
$sheetIterator->end();
|
233 |
+
}
|
234 |
+
|
235 |
+
$this->isStreamOpened = false;
|
236 |
+
}
|
237 |
+
}
|
238 |
+
}
|
app/Services/Spout/Reader/CSV/Reader.php
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\CSV;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\AbstractReader;
|
6 |
+
use Box\Spout\Common\Exception\IOException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class Reader
|
10 |
+
* This class provides support to read data from a CSV file.
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\CSV
|
13 |
+
*/
|
14 |
+
class Reader extends AbstractReader
|
15 |
+
{
|
16 |
+
/** @var resource Pointer to the file to be written */
|
17 |
+
protected $filePointer;
|
18 |
+
|
19 |
+
/** @var SheetIterator To iterator over the CSV unique "sheet" */
|
20 |
+
protected $sheetIterator;
|
21 |
+
|
22 |
+
/** @var string Original value for the "auto_detect_line_endings" INI value */
|
23 |
+
protected $originalAutoDetectLineEndings;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Returns the reader's current options
|
27 |
+
*
|
28 |
+
* @return ReaderOptions
|
29 |
+
*/
|
30 |
+
protected function getOptions()
|
31 |
+
{
|
32 |
+
if (!isset($this->options)) {
|
33 |
+
$this->options = new ReaderOptions();
|
34 |
+
}
|
35 |
+
return $this->options;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Sets the field delimiter for the CSV.
|
40 |
+
* Needs to be called before opening the reader.
|
41 |
+
*
|
42 |
+
* @param string $fieldDelimiter Character that delimits fields
|
43 |
+
* @return Reader
|
44 |
+
*/
|
45 |
+
public function setFieldDelimiter($fieldDelimiter)
|
46 |
+
{
|
47 |
+
$this->getOptions()->setFieldDelimiter($fieldDelimiter);
|
48 |
+
return $this;
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Sets the field enclosure for the CSV.
|
53 |
+
* Needs to be called before opening the reader.
|
54 |
+
*
|
55 |
+
* @param string $fieldEnclosure Character that enclose fields
|
56 |
+
* @return Reader
|
57 |
+
*/
|
58 |
+
public function setFieldEnclosure($fieldEnclosure)
|
59 |
+
{
|
60 |
+
$this->getOptions()->setFieldEnclosure($fieldEnclosure);
|
61 |
+
return $this;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Sets the encoding of the CSV file to be read.
|
66 |
+
* Needs to be called before opening the reader.
|
67 |
+
*
|
68 |
+
* @param string $encoding Encoding of the CSV file to be read
|
69 |
+
* @return Reader
|
70 |
+
*/
|
71 |
+
public function setEncoding($encoding)
|
72 |
+
{
|
73 |
+
$this->getOptions()->setEncoding($encoding);
|
74 |
+
return $this;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Sets the EOL for the CSV.
|
79 |
+
* Needs to be called before opening the reader.
|
80 |
+
*
|
81 |
+
* @param string $endOfLineCharacter used to properly get lines from the CSV file.
|
82 |
+
* @return Reader
|
83 |
+
*/
|
84 |
+
public function setEndOfLineCharacter($endOfLineCharacter)
|
85 |
+
{
|
86 |
+
$this->getOptions()->setEndOfLineCharacter($endOfLineCharacter);
|
87 |
+
return $this;
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Returns whether stream wrappers are supported
|
92 |
+
*
|
93 |
+
* @return bool
|
94 |
+
*/
|
95 |
+
protected function doesSupportStreamWrapper()
|
96 |
+
{
|
97 |
+
return true;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Opens the file at the given path to make it ready to be read.
|
102 |
+
* If setEncoding() was not called, it assumes that the file is encoded in UTF-8.
|
103 |
+
*
|
104 |
+
* @param string $filePath Path of the CSV file to be read
|
105 |
+
* @return void
|
106 |
+
* @throws \Box\Spout\Common\Exception\IOException
|
107 |
+
*/
|
108 |
+
protected function openReader($filePath)
|
109 |
+
{
|
110 |
+
$this->originalAutoDetectLineEndings = ini_get('auto_detect_line_endings');
|
111 |
+
ini_set('auto_detect_line_endings', '1');
|
112 |
+
|
113 |
+
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
|
114 |
+
if (!$this->filePointer) {
|
115 |
+
throw new IOException("Could not open file $filePath for reading.");
|
116 |
+
}
|
117 |
+
|
118 |
+
$this->sheetIterator = new SheetIterator(
|
119 |
+
$this->filePointer,
|
120 |
+
$this->getOptions(),
|
121 |
+
$this->globalFunctionsHelper
|
122 |
+
);
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Returns an iterator to iterate over sheets.
|
127 |
+
*
|
128 |
+
* @return SheetIterator To iterate over sheets
|
129 |
+
*/
|
130 |
+
protected function getConcreteSheetIterator()
|
131 |
+
{
|
132 |
+
return $this->sheetIterator;
|
133 |
+
}
|
134 |
+
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Closes the reader. To be used after reading the file.
|
138 |
+
*
|
139 |
+
* @return void
|
140 |
+
*/
|
141 |
+
protected function closeReader()
|
142 |
+
{
|
143 |
+
if ($this->filePointer) {
|
144 |
+
$this->globalFunctionsHelper->fclose($this->filePointer);
|
145 |
+
}
|
146 |
+
|
147 |
+
ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
|
148 |
+
}
|
149 |
+
}
|
app/Services/Spout/Reader/CSV/ReaderOptions.php
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\CSV;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Helper\EncodingHelper;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ReaderOptions
|
9 |
+
* This class is used to customize the reader's behavior
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader\CSV
|
12 |
+
*/
|
13 |
+
class ReaderOptions extends \Box\Spout\Reader\Common\ReaderOptions
|
14 |
+
{
|
15 |
+
/** @var string Defines the character used to delimit fields (one character only) */
|
16 |
+
protected $fieldDelimiter = ',';
|
17 |
+
|
18 |
+
/** @var string Defines the character used to enclose fields (one character only) */
|
19 |
+
protected $fieldEnclosure = '"';
|
20 |
+
|
21 |
+
/** @var string Encoding of the CSV file to be read */
|
22 |
+
protected $encoding = EncodingHelper::ENCODING_UTF8;
|
23 |
+
|
24 |
+
/** @var string Defines the End of line */
|
25 |
+
protected $endOfLineCharacter = "\n";
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @return string
|
29 |
+
*/
|
30 |
+
public function getFieldDelimiter()
|
31 |
+
{
|
32 |
+
return $this->fieldDelimiter;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Sets the field delimiter for the CSV.
|
37 |
+
* Needs to be called before opening the reader.
|
38 |
+
*
|
39 |
+
* @param string $fieldDelimiter Character that delimits fields
|
40 |
+
* @return ReaderOptions
|
41 |
+
*/
|
42 |
+
public function setFieldDelimiter($fieldDelimiter)
|
43 |
+
{
|
44 |
+
$this->fieldDelimiter = $fieldDelimiter;
|
45 |
+
return $this;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* @return string
|
50 |
+
*/
|
51 |
+
public function getFieldEnclosure()
|
52 |
+
{
|
53 |
+
return $this->fieldEnclosure;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Sets the field enclosure for the CSV.
|
58 |
+
* Needs to be called before opening the reader.
|
59 |
+
*
|
60 |
+
* @param string $fieldEnclosure Character that enclose fields
|
61 |
+
* @return ReaderOptions
|
62 |
+
*/
|
63 |
+
public function setFieldEnclosure($fieldEnclosure)
|
64 |
+
{
|
65 |
+
$this->fieldEnclosure = $fieldEnclosure;
|
66 |
+
return $this;
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* @return string
|
71 |
+
*/
|
72 |
+
public function getEncoding()
|
73 |
+
{
|
74 |
+
return $this->encoding;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Sets the encoding of the CSV file to be read.
|
79 |
+
* Needs to be called before opening the reader.
|
80 |
+
*
|
81 |
+
* @param string $encoding Encoding of the CSV file to be read
|
82 |
+
* @return ReaderOptions
|
83 |
+
*/
|
84 |
+
public function setEncoding($encoding)
|
85 |
+
{
|
86 |
+
$this->encoding = $encoding;
|
87 |
+
return $this;
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* @return string EOL for the CSV
|
92 |
+
*/
|
93 |
+
public function getEndOfLineCharacter()
|
94 |
+
{
|
95 |
+
return $this->endOfLineCharacter;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Sets the EOL for the CSV.
|
100 |
+
* Needs to be called before opening the reader.
|
101 |
+
*
|
102 |
+
* @param string $endOfLineCharacter used to properly get lines from the CSV file.
|
103 |
+
* @return ReaderOptions
|
104 |
+
*/
|
105 |
+
public function setEndOfLineCharacter($endOfLineCharacter)
|
106 |
+
{
|
107 |
+
$this->endOfLineCharacter = $endOfLineCharacter;
|
108 |
+
return $this;
|
109 |
+
}
|
110 |
+
}
|
app/Services/Spout/Reader/CSV/RowIterator.php
ADDED
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\CSV;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\IteratorInterface;
|
6 |
+
use Box\Spout\Common\Helper\EncodingHelper;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class RowIterator
|
10 |
+
* Iterate over CSV rows.
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\CSV
|
13 |
+
*/
|
14 |
+
class RowIterator implements IteratorInterface
|
15 |
+
{
|
16 |
+
/**
|
17 |
+
* Value passed to fgetcsv. 0 means "unlimited" (slightly slower but accomodates for very long lines).
|
18 |
+
*/
|
19 |
+
const MAX_READ_BYTES_PER_LINE = 0;
|
20 |
+
|
21 |
+
/** @var resource Pointer to the CSV file to read */
|
22 |
+
protected $filePointer;
|
23 |
+
|
24 |
+
/** @var int Number of read rows */
|
25 |
+
protected $numReadRows = 0;
|
26 |
+
|
27 |
+
/** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
|
28 |
+
protected $rowDataBuffer = null;
|
29 |
+
|
30 |
+
/** @var bool Indicates whether all rows have been read */
|
31 |
+
protected $hasReachedEndOfFile = false;
|
32 |
+
|
33 |
+
/** @var string Defines the character used to delimit fields (one character only) */
|
34 |
+
protected $fieldDelimiter;
|
35 |
+
|
36 |
+
/** @var string Defines the character used to enclose fields (one character only) */
|
37 |
+
protected $fieldEnclosure;
|
38 |
+
|
39 |
+
/** @var string Encoding of the CSV file to be read */
|
40 |
+
protected $encoding;
|
41 |
+
|
42 |
+
/** @var string End of line delimiter, given by the user as input. */
|
43 |
+
protected $inputEOLDelimiter;
|
44 |
+
|
45 |
+
/** @var bool Whether empty rows should be returned or skipped */
|
46 |
+
protected $shouldPreserveEmptyRows;
|
47 |
+
|
48 |
+
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
49 |
+
protected $globalFunctionsHelper;
|
50 |
+
|
51 |
+
/** @var \Box\Spout\Common\Helper\EncodingHelper Helper to work with different encodings */
|
52 |
+
protected $encodingHelper;
|
53 |
+
|
54 |
+
/** @var string End of line delimiter, encoded using the same encoding as the CSV */
|
55 |
+
protected $encodedEOLDelimiter;
|
56 |
+
|
57 |
+
/**
|
58 |
+
* @param resource $filePointer Pointer to the CSV file to read
|
59 |
+
* @param \Box\Spout\Reader\CSV\ReaderOptions $options
|
60 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
61 |
+
*/
|
62 |
+
public function __construct($filePointer, $options, $globalFunctionsHelper)
|
63 |
+
{
|
64 |
+
$this->filePointer = $filePointer;
|
65 |
+
$this->fieldDelimiter = $options->getFieldDelimiter();
|
66 |
+
$this->fieldEnclosure = $options->getFieldEnclosure();
|
67 |
+
$this->encoding = $options->getEncoding();
|
68 |
+
$this->inputEOLDelimiter = $options->getEndOfLineCharacter();
|
69 |
+
$this->shouldPreserveEmptyRows = $options->shouldPreserveEmptyRows();
|
70 |
+
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
71 |
+
|
72 |
+
$this->encodingHelper = new EncodingHelper($globalFunctionsHelper);
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Rewind the Iterator to the first element
|
77 |
+
* @link http://php.net/manual/en/iterator.rewind.php
|
78 |
+
*
|
79 |
+
* @return void
|
80 |
+
*/
|
81 |
+
public function rewind()
|
82 |
+
{
|
83 |
+
$this->rewindAndSkipBom();
|
84 |
+
|
85 |
+
$this->numReadRows = 0;
|
86 |
+
$this->rowDataBuffer = null;
|
87 |
+
|
88 |
+
$this->next();
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* This rewinds and skips the BOM if inserted at the beginning of the file
|
93 |
+
* by moving the file pointer after it, so that it is not read.
|
94 |
+
*
|
95 |
+
* @return void
|
96 |
+
*/
|
97 |
+
protected function rewindAndSkipBom()
|
98 |
+
{
|
99 |
+
$byteOffsetToSkipBom = $this->encodingHelper->getBytesOffsetToSkipBOM($this->filePointer, $this->encoding);
|
100 |
+
|
101 |
+
// sets the cursor after the BOM (0 means no BOM, so rewind it)
|
102 |
+
$this->globalFunctionsHelper->fseek($this->filePointer, $byteOffsetToSkipBom);
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Checks if current position is valid
|
107 |
+
* @link http://php.net/manual/en/iterator.valid.php
|
108 |
+
*
|
109 |
+
* @return bool
|
110 |
+
*/
|
111 |
+
public function valid()
|
112 |
+
{
|
113 |
+
return ($this->filePointer && !$this->hasReachedEndOfFile);
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Move forward to next element. Reads data for the next unprocessed row.
|
118 |
+
* @link http://php.net/manual/en/iterator.next.php
|
119 |
+
*
|
120 |
+
* @return void
|
121 |
+
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
122 |
+
*/
|
123 |
+
public function next()
|
124 |
+
{
|
125 |
+
$this->hasReachedEndOfFile = $this->globalFunctionsHelper->feof($this->filePointer);
|
126 |
+
|
127 |
+
if (!$this->hasReachedEndOfFile) {
|
128 |
+
$this->readDataForNextRow();
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* @return void
|
134 |
+
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
135 |
+
*/
|
136 |
+
protected function readDataForNextRow()
|
137 |
+
{
|
138 |
+
do {
|
139 |
+
$rowData = $this->getNextUTF8EncodedRow();
|
140 |
+
} while ($this->shouldReadNextRow($rowData));
|
141 |
+
|
142 |
+
if ($rowData !== false) {
|
143 |
+
// str_replace will replace NULL values by empty strings
|
144 |
+
$this->rowDataBuffer = str_replace(null, null, $rowData);
|
145 |
+
$this->numReadRows++;
|
146 |
+
} else {
|
147 |
+
// If we reach this point, it means end of file was reached.
|
148 |
+
// This happens when the last lines are empty lines.
|
149 |
+
$this->hasReachedEndOfFile = true;
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
/**
|
154 |
+
* @param array|bool $currentRowData
|
155 |
+
* @return bool Whether the data for the current row can be returned or if we need to keep reading
|
156 |
+
*/
|
157 |
+
protected function shouldReadNextRow($currentRowData)
|
158 |
+
{
|
159 |
+
$hasSuccessfullyFetchedRowData = ($currentRowData !== false);
|
160 |
+
$hasNowReachedEndOfFile = $this->globalFunctionsHelper->feof($this->filePointer);
|
161 |
+
$isEmptyLine = $this->isEmptyLine($currentRowData);
|
162 |
+
|
163 |
+
return (
|
164 |
+
(!$hasSuccessfullyFetchedRowData && !$hasNowReachedEndOfFile) ||
|
165 |
+
(!$this->shouldPreserveEmptyRows && $isEmptyLine)
|
166 |
+
);
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Returns the next row, converted if necessary to UTF-8.
|
171 |
+
* As fgetcsv() does not manage correctly encoding for non UTF-8 data,
|
172 |
+
* we remove manually whitespace with ltrim or rtrim (depending on the order of the bytes)
|
173 |
+
*
|
174 |
+
* @return array|false The row for the current file pointer, encoded in UTF-8 or FALSE if nothing to read
|
175 |
+
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
176 |
+
*/
|
177 |
+
protected function getNextUTF8EncodedRow()
|
178 |
+
{
|
179 |
+
$encodedRowData = $this->globalFunctionsHelper->fgetcsv($this->filePointer, self::MAX_READ_BYTES_PER_LINE, $this->fieldDelimiter, $this->fieldEnclosure);
|
180 |
+
if ($encodedRowData === false) {
|
181 |
+
return false;
|
182 |
+
}
|
183 |
+
|
184 |
+
foreach ($encodedRowData as $cellIndex => $cellValue) {
|
185 |
+
switch($this->encoding) {
|
186 |
+
case EncodingHelper::ENCODING_UTF16_LE:
|
187 |
+
case EncodingHelper::ENCODING_UTF32_LE:
|
188 |
+
// remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
|
189 |
+
$cellValue = ltrim($cellValue);
|
190 |
+
break;
|
191 |
+
|
192 |
+
case EncodingHelper::ENCODING_UTF16_BE:
|
193 |
+
case EncodingHelper::ENCODING_UTF32_BE:
|
194 |
+
// remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
|
195 |
+
$cellValue = rtrim($cellValue);
|
196 |
+
break;
|
197 |
+
}
|
198 |
+
|
199 |
+
$encodedRowData[$cellIndex] = $this->encodingHelper->attemptConversionToUTF8($cellValue, $this->encoding);
|
200 |
+
}
|
201 |
+
|
202 |
+
return $encodedRowData;
|
203 |
+
}
|
204 |
+
|
205 |
+
/**
|
206 |
+
* Returns the end of line delimiter, encoded using the same encoding as the CSV.
|
207 |
+
* The return value is cached.
|
208 |
+
*
|
209 |
+
* @return string
|
210 |
+
*/
|
211 |
+
protected function getEncodedEOLDelimiter()
|
212 |
+
{
|
213 |
+
if (!isset($this->encodedEOLDelimiter)) {
|
214 |
+
$this->encodedEOLDelimiter = $this->encodingHelper->attemptConversionFromUTF8($this->inputEOLDelimiter, $this->encoding);
|
215 |
+
}
|
216 |
+
|
217 |
+
return $this->encodedEOLDelimiter;
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* @param array|bool $lineData Array containing the cells value for the line
|
222 |
+
* @return bool Whether the given line is empty
|
223 |
+
*/
|
224 |
+
protected function isEmptyLine($lineData)
|
225 |
+
{
|
226 |
+
return (is_array($lineData) && count($lineData) === 1 && $lineData[0] === null);
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Return the current element from the buffer
|
231 |
+
* @link http://php.net/manual/en/iterator.current.php
|
232 |
+
*
|
233 |
+
* @return array|null
|
234 |
+
*/
|
235 |
+
public function current()
|
236 |
+
{
|
237 |
+
return $this->rowDataBuffer;
|
238 |
+
}
|
239 |
+
|
240 |
+
/**
|
241 |
+
* Return the key of the current element
|
242 |
+
* @link http://php.net/manual/en/iterator.key.php
|
243 |
+
*
|
244 |
+
* @return int
|
245 |
+
*/
|
246 |
+
public function key()
|
247 |
+
{
|
248 |
+
return $this->numReadRows;
|
249 |
+
}
|
250 |
+
|
251 |
+
/**
|
252 |
+
* Cleans up what was created to iterate over the object.
|
253 |
+
*
|
254 |
+
* @return void
|
255 |
+
*/
|
256 |
+
public function end()
|
257 |
+
{
|
258 |
+
// do nothing
|
259 |
+
}
|
260 |
+
}
|
app/Services/Spout/Reader/CSV/Sheet.php
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\CSV;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\SheetInterface;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class Sheet
|
9 |
+
*
|
10 |
+
* @package Box\Spout\Reader\CSV
|
11 |
+
*/
|
12 |
+
class Sheet implements SheetInterface
|
13 |
+
{
|
14 |
+
/** @var \Box\Spout\Reader\CSV\RowIterator To iterate over the CSV's rows */
|
15 |
+
protected $rowIterator;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* @param resource $filePointer Pointer to the CSV file to read
|
19 |
+
* @param \Box\Spout\Reader\CSV\ReaderOptions $options
|
20 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
21 |
+
*/
|
22 |
+
public function __construct($filePointer, $options, $globalFunctionsHelper)
|
23 |
+
{
|
24 |
+
$this->rowIterator = new RowIterator($filePointer, $options, $globalFunctionsHelper);
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @api
|
29 |
+
* @return \Box\Spout\Reader\CSV\RowIterator
|
30 |
+
*/
|
31 |
+
public function getRowIterator()
|
32 |
+
{
|
33 |
+
return $this->rowIterator;
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* @api
|
38 |
+
* @return int Index of the sheet
|
39 |
+
*/
|
40 |
+
public function getIndex()
|
41 |
+
{
|
42 |
+
return 0;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* @api
|
47 |
+
* @return string Name of the sheet - empty string since CSV does not support that
|
48 |
+
*/
|
49 |
+
public function getName()
|
50 |
+
{
|
51 |
+
return '';
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* @api
|
56 |
+
* @return bool Always TRUE as there is only one sheet
|
57 |
+
*/
|
58 |
+
public function isActive()
|
59 |
+
{
|
60 |
+
return true;
|
61 |
+
}
|
62 |
+
}
|
app/Services/Spout/Reader/CSV/SheetIterator.php
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\CSV;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\IteratorInterface;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class SheetIterator
|
9 |
+
* Iterate over CSV unique "sheet".
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader\CSV
|
12 |
+
*/
|
13 |
+
class SheetIterator implements IteratorInterface
|
14 |
+
{
|
15 |
+
/** @var \Box\Spout\Reader\CSV\Sheet The CSV unique "sheet" */
|
16 |
+
protected $sheet;
|
17 |
+
|
18 |
+
/** @var bool Whether the unique "sheet" has already been read */
|
19 |
+
protected $hasReadUniqueSheet = false;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @param resource $filePointer
|
23 |
+
* @param \Box\Spout\Reader\CSV\ReaderOptions $options
|
24 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
25 |
+
*/
|
26 |
+
public function __construct($filePointer, $options, $globalFunctionsHelper)
|
27 |
+
{
|
28 |
+
$this->sheet = new Sheet($filePointer, $options, $globalFunctionsHelper);
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Rewind the Iterator to the first element
|
33 |
+
* @link http://php.net/manual/en/iterator.rewind.php
|
34 |
+
*
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
public function rewind()
|
38 |
+
{
|
39 |
+
$this->hasReadUniqueSheet = false;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Checks if current position is valid
|
44 |
+
* @link http://php.net/manual/en/iterator.valid.php
|
45 |
+
*
|
46 |
+
* @return bool
|
47 |
+
*/
|
48 |
+
public function valid()
|
49 |
+
{
|
50 |
+
return (!$this->hasReadUniqueSheet);
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Move forward to next element
|
55 |
+
* @link http://php.net/manual/en/iterator.next.php
|
56 |
+
*
|
57 |
+
* @return void
|
58 |
+
*/
|
59 |
+
public function next()
|
60 |
+
{
|
61 |
+
$this->hasReadUniqueSheet = true;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Return the current element
|
66 |
+
* @link http://php.net/manual/en/iterator.current.php
|
67 |
+
*
|
68 |
+
* @return \Box\Spout\Reader\CSV\Sheet
|
69 |
+
*/
|
70 |
+
public function current()
|
71 |
+
{
|
72 |
+
return $this->sheet;
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Return the key of the current element
|
77 |
+
* @link http://php.net/manual/en/iterator.key.php
|
78 |
+
*
|
79 |
+
* @return int
|
80 |
+
*/
|
81 |
+
public function key()
|
82 |
+
{
|
83 |
+
return 1;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Cleans up what was created to iterate over the object.
|
88 |
+
*
|
89 |
+
* @return void
|
90 |
+
*/
|
91 |
+
public function end()
|
92 |
+
{
|
93 |
+
// do nothing
|
94 |
+
}
|
95 |
+
}
|
app/Services/Spout/Reader/Common/ReaderOptions.php
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Common;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class ReaderOptions
|
7 |
+
* Readers' common options
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Reader\Common
|
10 |
+
*/
|
11 |
+
class ReaderOptions
|
12 |
+
{
|
13 |
+
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
14 |
+
protected $shouldFormatDates = false;
|
15 |
+
|
16 |
+
/** @var bool Whether empty rows should be returned or skipped */
|
17 |
+
protected $shouldPreserveEmptyRows = false;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @return bool Whether date/time values should be returned as PHP objects or be formatted as strings.
|
21 |
+
*/
|
22 |
+
public function shouldFormatDates()
|
23 |
+
{
|
24 |
+
return $this->shouldFormatDates;
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Sets whether date/time values should be returned as PHP objects or be formatted as strings.
|
29 |
+
*
|
30 |
+
* @param bool $shouldFormatDates
|
31 |
+
* @return ReaderOptions
|
32 |
+
*/
|
33 |
+
public function setShouldFormatDates($shouldFormatDates)
|
34 |
+
{
|
35 |
+
$this->shouldFormatDates = $shouldFormatDates;
|
36 |
+
return $this;
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @return bool Whether empty rows should be returned or skipped.
|
41 |
+
*/
|
42 |
+
public function shouldPreserveEmptyRows()
|
43 |
+
{
|
44 |
+
return $this->shouldPreserveEmptyRows;
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Sets whether empty rows should be returned or skipped.
|
49 |
+
*
|
50 |
+
* @param bool $shouldPreserveEmptyRows
|
51 |
+
* @return ReaderOptions
|
52 |
+
*/
|
53 |
+
public function setShouldPreserveEmptyRows($shouldPreserveEmptyRows)
|
54 |
+
{
|
55 |
+
$this->shouldPreserveEmptyRows = $shouldPreserveEmptyRows;
|
56 |
+
return $this;
|
57 |
+
}
|
58 |
+
}
|
app/Services/Spout/Reader/Common/XMLProcessor.php
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Common;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class XMLProcessor
|
9 |
+
* Helps process XML files
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader\Common
|
12 |
+
*/
|
13 |
+
class XMLProcessor
|
14 |
+
{
|
15 |
+
/* Node types */
|
16 |
+
const NODE_TYPE_START = XMLReader::ELEMENT;
|
17 |
+
const NODE_TYPE_END = XMLReader::END_ELEMENT;
|
18 |
+
|
19 |
+
/* Keys associated to reflection attributes to invoke a callback */
|
20 |
+
const CALLBACK_REFLECTION_METHOD = 'reflectionMethod';
|
21 |
+
const CALLBACK_REFLECTION_OBJECT = 'reflectionObject';
|
22 |
+
|
23 |
+
/* Values returned by the callbacks to indicate what the processor should do next */
|
24 |
+
const PROCESSING_CONTINUE = 1;
|
25 |
+
const PROCESSING_STOP = 2;
|
26 |
+
|
27 |
+
|
28 |
+
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
29 |
+
protected $xmlReader;
|
30 |
+
|
31 |
+
/** @var array Registered callbacks */
|
32 |
+
private $callbacks = [];
|
33 |
+
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object
|
37 |
+
*/
|
38 |
+
public function __construct($xmlReader)
|
39 |
+
{
|
40 |
+
$this->xmlReader = $xmlReader;
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* @param string $nodeName A callback may be triggered when a node with this name is read
|
45 |
+
* @param int $nodeType Type of the node [NODE_TYPE_START || NODE_TYPE_END]
|
46 |
+
* @param callable $callback Callback to execute when the read node has the given name and type
|
47 |
+
* @return XMLProcessor
|
48 |
+
*/
|
49 |
+
public function registerCallback($nodeName, $nodeType, $callback)
|
50 |
+
{
|
51 |
+
$callbackKey = $this->getCallbackKey($nodeName, $nodeType);
|
52 |
+
$this->callbacks[$callbackKey] = $this->getInvokableCallbackData($callback);
|
53 |
+
|
54 |
+
return $this;
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* @param string $nodeName Name of the node
|
59 |
+
* @param int $nodeType Type of the node [NODE_TYPE_START || NODE_TYPE_END]
|
60 |
+
* @return string Key used to store the associated callback
|
61 |
+
*/
|
62 |
+
private function getCallbackKey($nodeName, $nodeType)
|
63 |
+
{
|
64 |
+
return "$nodeName$nodeType";
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Because the callback can be a "protected" function, we don't want to use call_user_func() directly
|
69 |
+
* but instead invoke the callback using Reflection. This allows the invocation of "protected" functions.
|
70 |
+
* Since some functions can be called a lot, we pre-process the callback to only return the elements that
|
71 |
+
* will be needed to invoke the callback later.
|
72 |
+
*
|
73 |
+
* @param callable $callback Array reference to a callback: [OBJECT, METHOD_NAME]
|
74 |
+
* @return array Associative array containing the elements needed to invoke the callback using Reflection
|
75 |
+
*/
|
76 |
+
private function getInvokableCallbackData($callback)
|
77 |
+
{
|
78 |
+
$callbackObject = $callback[0];
|
79 |
+
$callbackMethodName = $callback[1];
|
80 |
+
$reflectionMethod = new \ReflectionMethod(get_class($callbackObject), $callbackMethodName);
|
81 |
+
$reflectionMethod->setAccessible(true);
|
82 |
+
|
83 |
+
return [
|
84 |
+
self::CALLBACK_REFLECTION_METHOD => $reflectionMethod,
|
85 |
+
self::CALLBACK_REFLECTION_OBJECT => $callbackObject,
|
86 |
+
];
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Resumes the reading of the XML file where it was left off.
|
91 |
+
* Stops whenever a callback indicates that reading should stop or at the end of the file.
|
92 |
+
*
|
93 |
+
* @return void
|
94 |
+
* @throws \Box\Spout\Reader\Exception\XMLProcessingException
|
95 |
+
*/
|
96 |
+
public function readUntilStopped()
|
97 |
+
{
|
98 |
+
while ($this->xmlReader->read()) {
|
99 |
+
$nodeType = $this->xmlReader->nodeType;
|
100 |
+
$nodeNamePossiblyWithPrefix = $this->xmlReader->name;
|
101 |
+
$nodeNameWithoutPrefix = $this->xmlReader->localName;
|
102 |
+
|
103 |
+
$callbackData = $this->getRegisteredCallbackData($nodeNamePossiblyWithPrefix, $nodeNameWithoutPrefix, $nodeType);
|
104 |
+
|
105 |
+
if ($callbackData !== null) {
|
106 |
+
$callbackResponse = $this->invokeCallback($callbackData, [$this->xmlReader]);
|
107 |
+
|
108 |
+
if ($callbackResponse === self::PROCESSING_STOP) {
|
109 |
+
// stop reading
|
110 |
+
break;
|
111 |
+
}
|
112 |
+
}
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* @param string $nodeNamePossiblyWithPrefix Name of the node, possibly prefixed
|
118 |
+
* @param string $nodeNameWithoutPrefix Name of the same node, un-prefixed
|
119 |
+
* @param int $nodeType Type of the node [NODE_TYPE_START || NODE_TYPE_END]
|
120 |
+
* @return array|null Callback data to be used for execution when a node of the given name/type is read or NULL if none found
|
121 |
+
*/
|
122 |
+
private function getRegisteredCallbackData($nodeNamePossiblyWithPrefix, $nodeNameWithoutPrefix, $nodeType)
|
123 |
+
{
|
124 |
+
// With prefixed nodes, we should match if (by order of preference):
|
125 |
+
// 1. the callback was registered with the prefixed node name (e.g. "x:worksheet")
|
126 |
+
// 2. the callback was registered with the un-prefixed node name (e.g. "worksheet")
|
127 |
+
$callbackKeyForPossiblyPrefixedName = $this->getCallbackKey($nodeNamePossiblyWithPrefix, $nodeType);
|
128 |
+
$callbackKeyForUnPrefixedName = $this->getCallbackKey($nodeNameWithoutPrefix, $nodeType);
|
129 |
+
$hasPrefix = ($nodeNamePossiblyWithPrefix !== $nodeNameWithoutPrefix);
|
130 |
+
|
131 |
+
$callbackKeyToUse = $callbackKeyForUnPrefixedName;
|
132 |
+
if ($hasPrefix && isset($this->callbacks[$callbackKeyForPossiblyPrefixedName])) {
|
133 |
+
$callbackKeyToUse = $callbackKeyForPossiblyPrefixedName;
|
134 |
+
}
|
135 |
+
|
136 |
+
// Using isset here because it is way faster than array_key_exists...
|
137 |
+
return isset($this->callbacks[$callbackKeyToUse]) ? $this->callbacks[$callbackKeyToUse] : null;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* @param array $callbackData Associative array containing data to invoke the callback using Reflection
|
142 |
+
* @param array $args Arguments to pass to the callback
|
143 |
+
* @return int Callback response
|
144 |
+
*/
|
145 |
+
private function invokeCallback($callbackData, $args)
|
146 |
+
{
|
147 |
+
$reflectionMethod = $callbackData[self::CALLBACK_REFLECTION_METHOD];
|
148 |
+
$callbackObject = $callbackData[self::CALLBACK_REFLECTION_OBJECT];
|
149 |
+
|
150 |
+
return $reflectionMethod->invokeArgs($callbackObject, $args);
|
151 |
+
}
|
152 |
+
}
|
app/Services/Spout/Reader/Exception/IteratorNotRewindableException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class IteratorNotRewindableException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Reader\Exception
|
10 |
+
*/
|
11 |
+
class IteratorNotRewindableException extends ReaderException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Reader/Exception/NoSheetsFoundException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class NoSheetsFoundException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Reader\Exception
|
10 |
+
*/
|
11 |
+
class NoSheetsFoundException extends ReaderException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Reader/Exception/ReaderException.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Exception;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\SpoutException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class ReaderException
|
9 |
+
*
|
10 |
+
* @package Box\Spout\Reader\Exception
|
11 |
+
* @abstract
|
12 |
+
*/
|
13 |
+
abstract class ReaderException extends SpoutException
|
14 |
+
{
|
15 |
+
}
|
app/Services/Spout/Reader/Exception/ReaderNotOpenedException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class ReaderNotOpenedException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Reader\Exception
|
10 |
+
*/
|
11 |
+
class ReaderNotOpenedException extends ReaderException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Reader/Exception/SharedStringNotFoundException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class SharedStringNotFoundException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Reader\Exception
|
10 |
+
*/
|
11 |
+
class SharedStringNotFoundException extends ReaderException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Reader/Exception/XMLProcessingException.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class XMLProcessingException
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Reader\Exception
|
9 |
+
*/
|
10 |
+
class XMLProcessingException extends ReaderException
|
11 |
+
{
|
12 |
+
}
|
app/Services/Spout/Reader/IteratorInterface.php
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface IteratorInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Reader
|
9 |
+
*/
|
10 |
+
interface IteratorInterface extends \Iterator
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Cleans up what was created to iterate over the object.
|
14 |
+
*
|
15 |
+
* @return void
|
16 |
+
*/
|
17 |
+
public function end();
|
18 |
+
}
|
app/Services/Spout/Reader/ODS/Helper/CellValueFormatter.php
ADDED
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class CellValueFormatter
|
7 |
+
* This class provides helper functions to format cell values
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Reader\ODS\Helper
|
10 |
+
*/
|
11 |
+
class CellValueFormatter
|
12 |
+
{
|
13 |
+
/** Definition of all possible cell types */
|
14 |
+
const CELL_TYPE_STRING = 'string';
|
15 |
+
const CELL_TYPE_FLOAT = 'float';
|
16 |
+
const CELL_TYPE_BOOLEAN = 'boolean';
|
17 |
+
const CELL_TYPE_DATE = 'date';
|
18 |
+
const CELL_TYPE_TIME = 'time';
|
19 |
+
const CELL_TYPE_CURRENCY = 'currency';
|
20 |
+
const CELL_TYPE_PERCENTAGE = 'percentage';
|
21 |
+
const CELL_TYPE_VOID = 'void';
|
22 |
+
|
23 |
+
/** Definition of XML nodes names used to parse data */
|
24 |
+
const XML_NODE_P = 'p';
|
25 |
+
const XML_NODE_S = 'text:s';
|
26 |
+
const XML_NODE_A = 'text:a';
|
27 |
+
const XML_NODE_SPAN = 'text:span';
|
28 |
+
|
29 |
+
/** Definition of XML attributes used to parse data */
|
30 |
+
const XML_ATTRIBUTE_TYPE = 'office:value-type';
|
31 |
+
const XML_ATTRIBUTE_VALUE = 'office:value';
|
32 |
+
const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
|
33 |
+
const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
|
34 |
+
const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
|
35 |
+
const XML_ATTRIBUTE_CURRENCY = 'office:currency';
|
36 |
+
const XML_ATTRIBUTE_C = 'text:c';
|
37 |
+
|
38 |
+
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
39 |
+
protected $shouldFormatDates;
|
40 |
+
|
41 |
+
/** @var \Box\Spout\Common\Escaper\ODS Used to unescape XML data */
|
42 |
+
protected $escaper;
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
46 |
+
*/
|
47 |
+
public function __construct($shouldFormatDates)
|
48 |
+
{
|
49 |
+
$this->shouldFormatDates = $shouldFormatDates;
|
50 |
+
|
51 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
52 |
+
$this->escaper = \Box\Spout\Common\Escaper\ODS::getInstance();
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
57 |
+
* @see http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
|
58 |
+
*
|
59 |
+
* @param \DOMNode $node
|
60 |
+
* @return string|int|float|bool|\DateTime|\DateInterval|null The value associated with the cell, empty string if cell's type is void/undefined, null on error
|
61 |
+
*/
|
62 |
+
public function extractAndFormatNodeValue($node)
|
63 |
+
{
|
64 |
+
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE);
|
65 |
+
|
66 |
+
switch ($cellType) {
|
67 |
+
case self::CELL_TYPE_STRING:
|
68 |
+
return $this->formatStringCellValue($node);
|
69 |
+
case self::CELL_TYPE_FLOAT:
|
70 |
+
return $this->formatFloatCellValue($node);
|
71 |
+
case self::CELL_TYPE_BOOLEAN:
|
72 |
+
return $this->formatBooleanCellValue($node);
|
73 |
+
case self::CELL_TYPE_DATE:
|
74 |
+
return $this->formatDateCellValue($node);
|
75 |
+
case self::CELL_TYPE_TIME:
|
76 |
+
return $this->formatTimeCellValue($node);
|
77 |
+
case self::CELL_TYPE_CURRENCY:
|
78 |
+
return $this->formatCurrencyCellValue($node);
|
79 |
+
case self::CELL_TYPE_PERCENTAGE:
|
80 |
+
return $this->formatPercentageCellValue($node);
|
81 |
+
case self::CELL_TYPE_VOID:
|
82 |
+
default:
|
83 |
+
return '';
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Returns the cell String value.
|
89 |
+
*
|
90 |
+
* @param \DOMNode $node
|
91 |
+
* @return string The value associated with the cell
|
92 |
+
*/
|
93 |
+
protected function formatStringCellValue($node)
|
94 |
+
{
|
95 |
+
$pNodeValues = [];
|
96 |
+
$pNodes = $node->getElementsByTagName(self::XML_NODE_P);
|
97 |
+
|
98 |
+
foreach ($pNodes as $pNode) {
|
99 |
+
$currentPValue = '';
|
100 |
+
|
101 |
+
foreach ($pNode->childNodes as $childNode) {
|
102 |
+
if ($childNode instanceof \DOMText) {
|
103 |
+
$currentPValue .= $childNode->nodeValue;
|
104 |
+
} else if ($childNode->nodeName === self::XML_NODE_S) {
|
105 |
+
$spaceAttribute = $childNode->getAttribute(self::XML_ATTRIBUTE_C);
|
106 |
+
$numSpaces = (!empty($spaceAttribute)) ? intval($spaceAttribute) : 1;
|
107 |
+
$currentPValue .= str_repeat(' ', $numSpaces);
|
108 |
+
} else if ($childNode->nodeName === self::XML_NODE_A || $childNode->nodeName === self::XML_NODE_SPAN) {
|
109 |
+
$currentPValue .= $childNode->nodeValue;
|
110 |
+
}
|
111 |
+
}
|
112 |
+
|
113 |
+
$pNodeValues[] = $currentPValue;
|
114 |
+
}
|
115 |
+
|
116 |
+
$escapedCellValue = implode("\n", $pNodeValues);
|
117 |
+
$cellValue = $this->escaper->unescape($escapedCellValue);
|
118 |
+
return $cellValue;
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Returns the cell Numeric value from the given node.
|
123 |
+
*
|
124 |
+
* @param \DOMNode $node
|
125 |
+
* @return int|float The value associated with the cell
|
126 |
+
*/
|
127 |
+
protected function formatFloatCellValue($node)
|
128 |
+
{
|
129 |
+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
|
130 |
+
$nodeIntValue = intval($nodeValue);
|
131 |
+
// The "==" is intentionally not a "===" because only the value matters, not the type
|
132 |
+
$cellValue = ($nodeIntValue == $nodeValue) ? $nodeIntValue : floatval($nodeValue);
|
133 |
+
return $cellValue;
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Returns the cell Boolean value from the given node.
|
138 |
+
*
|
139 |
+
* @param \DOMNode $node
|
140 |
+
* @return bool The value associated with the cell
|
141 |
+
*/
|
142 |
+
protected function formatBooleanCellValue($node)
|
143 |
+
{
|
144 |
+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_BOOLEAN_VALUE);
|
145 |
+
// !! is similar to boolval()
|
146 |
+
$cellValue = !!$nodeValue;
|
147 |
+
return $cellValue;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Returns the cell Date value from the given node.
|
152 |
+
*
|
153 |
+
* @param \DOMNode $node
|
154 |
+
* @return \DateTime|string|null The value associated with the cell or NULL if invalid date value
|
155 |
+
*/
|
156 |
+
protected function formatDateCellValue($node)
|
157 |
+
{
|
158 |
+
// The XML node looks like this:
|
159 |
+
// <table:table-cell calcext:value-type="date" office:date-value="2016-05-19T16:39:00" office:value-type="date">
|
160 |
+
// <text:p>05/19/16 04:39 PM</text:p>
|
161 |
+
// </table:table-cell>
|
162 |
+
|
163 |
+
if ($this->shouldFormatDates) {
|
164 |
+
// The date is already formatted in the "p" tag
|
165 |
+
$nodeWithValueAlreadyFormatted = $node->getElementsByTagName(self::XML_NODE_P)->item(0);
|
166 |
+
return $nodeWithValueAlreadyFormatted->nodeValue;
|
167 |
+
} else {
|
168 |
+
// otherwise, get it from the "date-value" attribute
|
169 |
+
try {
|
170 |
+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_DATE_VALUE);
|
171 |
+
return new \DateTime($nodeValue);
|
172 |
+
} catch (\Exception $e) {
|
173 |
+
return null;
|
174 |
+
}
|
175 |
+
}
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Returns the cell Time value from the given node.
|
180 |
+
*
|
181 |
+
* @param \DOMNode $node
|
182 |
+
* @return \DateInterval|string|null The value associated with the cell or NULL if invalid time value
|
183 |
+
*/
|
184 |
+
protected function formatTimeCellValue($node)
|
185 |
+
{
|
186 |
+
// The XML node looks like this:
|
187 |
+
// <table:table-cell calcext:value-type="time" office:time-value="PT13H24M00S" office:value-type="time">
|
188 |
+
// <text:p>01:24:00 PM</text:p>
|
189 |
+
// </table:table-cell>
|
190 |
+
|
191 |
+
if ($this->shouldFormatDates) {
|
192 |
+
// The date is already formatted in the "p" tag
|
193 |
+
$nodeWithValueAlreadyFormatted = $node->getElementsByTagName(self::XML_NODE_P)->item(0);
|
194 |
+
return $nodeWithValueAlreadyFormatted->nodeValue;
|
195 |
+
} else {
|
196 |
+
// otherwise, get it from the "time-value" attribute
|
197 |
+
try {
|
198 |
+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_TIME_VALUE);
|
199 |
+
return new \DateInterval($nodeValue);
|
200 |
+
} catch (\Exception $e) {
|
201 |
+
return null;
|
202 |
+
}
|
203 |
+
}
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Returns the cell Currency value from the given node.
|
208 |
+
*
|
209 |
+
* @param \DOMNode $node
|
210 |
+
* @return string The value associated with the cell (e.g. "100 USD" or "9.99 EUR")
|
211 |
+
*/
|
212 |
+
protected function formatCurrencyCellValue($node)
|
213 |
+
{
|
214 |
+
$value = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
|
215 |
+
$currency = $node->getAttribute(self::XML_ATTRIBUTE_CURRENCY);
|
216 |
+
|
217 |
+
return "$value $currency";
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* Returns the cell Percentage value from the given node.
|
222 |
+
*
|
223 |
+
* @param \DOMNode $node
|
224 |
+
* @return int|float The value associated with the cell
|
225 |
+
*/
|
226 |
+
protected function formatPercentageCellValue($node)
|
227 |
+
{
|
228 |
+
// percentages are formatted like floats
|
229 |
+
return $this->formatFloatCellValue($node);
|
230 |
+
}
|
231 |
+
}
|
app/Services/Spout/Reader/ODS/Helper/SettingsHelper.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\Exception\XMLProcessingException;
|
6 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class SettingsHelper
|
10 |
+
* This class provides helper functions to extract data from the "settings.xml" file.
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\ODS\Helper
|
13 |
+
*/
|
14 |
+
class SettingsHelper
|
15 |
+
{
|
16 |
+
const SETTINGS_XML_FILE_PATH = 'settings.xml';
|
17 |
+
|
18 |
+
/** Definition of XML nodes name and attribute used to parse settings data */
|
19 |
+
const XML_NODE_CONFIG_ITEM = 'config:config-item';
|
20 |
+
const XML_ATTRIBUTE_CONFIG_NAME = 'config:name';
|
21 |
+
const XML_ATTRIBUTE_VALUE_ACTIVE_TABLE = 'ActiveTable';
|
22 |
+
|
23 |
+
/**
|
24 |
+
* @param string $filePath Path of the file to be read
|
25 |
+
* @return string|null Name of the sheet that was defined as active or NULL if none found
|
26 |
+
*/
|
27 |
+
public function getActiveSheetName($filePath)
|
28 |
+
{
|
29 |
+
$xmlReader = new XMLReader();
|
30 |
+
if ($xmlReader->openFileInZip($filePath, self::SETTINGS_XML_FILE_PATH) === false) {
|
31 |
+
return null;
|
32 |
+
}
|
33 |
+
|
34 |
+
$activeSheetName = null;
|
35 |
+
|
36 |
+
try {
|
37 |
+
while ($xmlReader->readUntilNodeFound(self::XML_NODE_CONFIG_ITEM)) {
|
38 |
+
if ($xmlReader->getAttribute(self::XML_ATTRIBUTE_CONFIG_NAME) === self::XML_ATTRIBUTE_VALUE_ACTIVE_TABLE) {
|
39 |
+
$activeSheetName = $xmlReader->readString();
|
40 |
+
break;
|
41 |
+
}
|
42 |
+
}
|
43 |
+
} catch (XMLProcessingException $exception) {
|
44 |
+
// do nothing
|
45 |
+
}
|
46 |
+
|
47 |
+
$xmlReader->close();
|
48 |
+
|
49 |
+
return $activeSheetName;
|
50 |
+
}
|
51 |
+
}
|
app/Services/Spout/Reader/ODS/Reader.php
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\AbstractReader;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class Reader
|
10 |
+
* This class provides support to read data from a ODS file
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\ODS
|
13 |
+
*/
|
14 |
+
class Reader extends AbstractReader
|
15 |
+
{
|
16 |
+
/** @var \ZipArchive */
|
17 |
+
protected $zip;
|
18 |
+
|
19 |
+
/** @var SheetIterator To iterator over the ODS sheets */
|
20 |
+
protected $sheetIterator;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Returns the reader's current options
|
24 |
+
*
|
25 |
+
* @return ReaderOptions
|
26 |
+
*/
|
27 |
+
protected function getOptions()
|
28 |
+
{
|
29 |
+
if (!isset($this->options)) {
|
30 |
+
$this->options = new ReaderOptions();
|
31 |
+
}
|
32 |
+
return $this->options;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Returns whether stream wrappers are supported
|
37 |
+
*
|
38 |
+
* @return bool
|
39 |
+
*/
|
40 |
+
protected function doesSupportStreamWrapper()
|
41 |
+
{
|
42 |
+
return false;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Opens the file at the given file path to make it ready to be read.
|
47 |
+
*
|
48 |
+
* @param string $filePath Path of the file to be read
|
49 |
+
* @return void
|
50 |
+
* @throws \Box\Spout\Common\Exception\IOException If the file at the given path or its content cannot be read
|
51 |
+
* @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
|
52 |
+
*/
|
53 |
+
protected function openReader($filePath)
|
54 |
+
{
|
55 |
+
$this->zip = new \ZipArchive();
|
56 |
+
|
57 |
+
if ($this->zip->open($filePath) === true) {
|
58 |
+
$this->sheetIterator = new SheetIterator($filePath, $this->getOptions());
|
59 |
+
} else {
|
60 |
+
throw new IOException("Could not open $filePath for reading.");
|
61 |
+
}
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Returns an iterator to iterate over sheets.
|
66 |
+
*
|
67 |
+
* @return SheetIterator To iterate over sheets
|
68 |
+
*/
|
69 |
+
protected function getConcreteSheetIterator()
|
70 |
+
{
|
71 |
+
return $this->sheetIterator;
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Closes the reader. To be used after reading the file.
|
76 |
+
*
|
77 |
+
* @return void
|
78 |
+
*/
|
79 |
+
protected function closeReader()
|
80 |
+
{
|
81 |
+
if ($this->zip) {
|
82 |
+
$this->zip->close();
|
83 |
+
}
|
84 |
+
}
|
85 |
+
}
|
app/Services/Spout/Reader/ODS/ReaderOptions.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class ReaderOptions
|
7 |
+
* This class is used to customize the reader's behavior
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Reader\ODS
|
10 |
+
*/
|
11 |
+
class ReaderOptions extends \Box\Spout\Reader\Common\ReaderOptions
|
12 |
+
{
|
13 |
+
// No extra options
|
14 |
+
}
|
app/Services/Spout/Reader/ODS/RowIterator.php
ADDED
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\Exception\IteratorNotRewindableException;
|
7 |
+
use Box\Spout\Reader\Exception\XMLProcessingException;
|
8 |
+
use Box\Spout\Reader\IteratorInterface;
|
9 |
+
use Box\Spout\Reader\ODS\Helper\CellValueFormatter;
|
10 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
11 |
+
use Box\Spout\Reader\Common\XMLProcessor;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Class RowIterator
|
15 |
+
*
|
16 |
+
* @package Box\Spout\Reader\ODS
|
17 |
+
*/
|
18 |
+
class RowIterator implements IteratorInterface
|
19 |
+
{
|
20 |
+
/** Definition of XML nodes names used to parse data */
|
21 |
+
const XML_NODE_TABLE = 'table:table';
|
22 |
+
const XML_NODE_ROW = 'table:table-row';
|
23 |
+
const XML_NODE_CELL = 'table:table-cell';
|
24 |
+
const MAX_COLUMNS_EXCEL = 16384;
|
25 |
+
|
26 |
+
/** Definition of XML attribute used to parse data */
|
27 |
+
const XML_ATTRIBUTE_NUM_ROWS_REPEATED = 'table:number-rows-repeated';
|
28 |
+
const XML_ATTRIBUTE_NUM_COLUMNS_REPEATED = 'table:number-columns-repeated';
|
29 |
+
|
30 |
+
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
31 |
+
protected $xmlReader;
|
32 |
+
|
33 |
+
/** @var \Box\Spout\Reader\Common\XMLProcessor Helper Object to process XML nodes */
|
34 |
+
protected $xmlProcessor;
|
35 |
+
|
36 |
+
/** @var bool Whether empty rows should be returned or skipped */
|
37 |
+
protected $shouldPreserveEmptyRows;
|
38 |
+
|
39 |
+
/** @var Helper\CellValueFormatter Helper to format cell values */
|
40 |
+
protected $cellValueFormatter;
|
41 |
+
|
42 |
+
/** @var bool Whether the iterator has already been rewound once */
|
43 |
+
protected $hasAlreadyBeenRewound = false;
|
44 |
+
|
45 |
+
/** @var array Contains the data for the currently processed row (key = cell index, value = cell value) */
|
46 |
+
protected $currentlyProcessedRowData = [];
|
47 |
+
|
48 |
+
/** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
|
49 |
+
protected $rowDataBuffer = null;
|
50 |
+
|
51 |
+
/** @var bool Indicates whether all rows have been read */
|
52 |
+
protected $hasReachedEndOfFile = false;
|
53 |
+
|
54 |
+
/** @var int Last row index processed (one-based) */
|
55 |
+
protected $lastRowIndexProcessed = 0;
|
56 |
+
|
57 |
+
/** @var int Row index to be processed next (one-based) */
|
58 |
+
protected $nextRowIndexToBeProcessed = 1;
|
59 |
+
|
60 |
+
/** @var mixed|null Value of the last processed cell (because when reading cell at column N+1, cell N is processed) */
|
61 |
+
protected $lastProcessedCellValue = null;
|
62 |
+
|
63 |
+
/** @var int Number of times the last processed row should be repeated */
|
64 |
+
protected $numRowsRepeated = 1;
|
65 |
+
|
66 |
+
/** @var int Number of times the last cell value should be copied to the cells on its right */
|
67 |
+
protected $numColumnsRepeated = 1;
|
68 |
+
|
69 |
+
/** @var bool Whether at least one cell has been read for the row currently being processed */
|
70 |
+
protected $hasAlreadyReadOneCellInCurrentRow = false;
|
71 |
+
|
72 |
+
|
73 |
+
/**
|
74 |
+
* @param XMLReader $xmlReader XML Reader, positioned on the "<table:table>" element
|
75 |
+
* @param \Box\Spout\Reader\ODS\ReaderOptions $options Reader's current options
|
76 |
+
*/
|
77 |
+
public function __construct($xmlReader, $options)
|
78 |
+
{
|
79 |
+
$this->xmlReader = $xmlReader;
|
80 |
+
$this->shouldPreserveEmptyRows = $options->shouldPreserveEmptyRows();
|
81 |
+
$this->cellValueFormatter = new CellValueFormatter($options->shouldFormatDates());
|
82 |
+
|
83 |
+
// Register all callbacks to process different nodes when reading the XML file
|
84 |
+
$this->xmlProcessor = new XMLProcessor($this->xmlReader);
|
85 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_ROW, XMLProcessor::NODE_TYPE_START, [$this, 'processRowStartingNode']);
|
86 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_CELL, XMLProcessor::NODE_TYPE_START, [$this, 'processCellStartingNode']);
|
87 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_ROW, XMLProcessor::NODE_TYPE_END, [$this, 'processRowEndingNode']);
|
88 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_TABLE, XMLProcessor::NODE_TYPE_END, [$this, 'processTableEndingNode']);
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Rewind the Iterator to the first element.
|
93 |
+
* NOTE: It can only be done once, as it is not possible to read an XML file backwards.
|
94 |
+
* @link http://php.net/manual/en/iterator.rewind.php
|
95 |
+
*
|
96 |
+
* @return void
|
97 |
+
* @throws \Box\Spout\Reader\Exception\IteratorNotRewindableException If the iterator is rewound more than once
|
98 |
+
*/
|
99 |
+
public function rewind()
|
100 |
+
{
|
101 |
+
// Because sheet and row data is located in the file, we can't rewind both the
|
102 |
+
// sheet iterator and the row iterator, as XML file cannot be read backwards.
|
103 |
+
// Therefore, rewinding the row iterator has been disabled.
|
104 |
+
if ($this->hasAlreadyBeenRewound) {
|
105 |
+
throw new IteratorNotRewindableException();
|
106 |
+
}
|
107 |
+
|
108 |
+
$this->hasAlreadyBeenRewound = true;
|
109 |
+
$this->lastRowIndexProcessed = 0;
|
110 |
+
$this->nextRowIndexToBeProcessed = 1;
|
111 |
+
$this->rowDataBuffer = null;
|
112 |
+
$this->hasReachedEndOfFile = false;
|
113 |
+
|
114 |
+
$this->next();
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Checks if current position is valid
|
119 |
+
* @link http://php.net/manual/en/iterator.valid.php
|
120 |
+
*
|
121 |
+
* @return bool
|
122 |
+
*/
|
123 |
+
public function valid()
|
124 |
+
{
|
125 |
+
return (!$this->hasReachedEndOfFile);
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Move forward to next element. Empty rows will be skipped.
|
130 |
+
* @link http://php.net/manual/en/iterator.next.php
|
131 |
+
*
|
132 |
+
* @return void
|
133 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
134 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
|
135 |
+
*/
|
136 |
+
public function next()
|
137 |
+
{
|
138 |
+
if ($this->doesNeedDataForNextRowToBeProcessed()) {
|
139 |
+
$this->readDataForNextRow();
|
140 |
+
}
|
141 |
+
|
142 |
+
$this->lastRowIndexProcessed++;
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Returns whether we need data for the next row to be processed.
|
147 |
+
* We DO need to read data if:
|
148 |
+
* - we have not read any rows yet
|
149 |
+
* OR
|
150 |
+
* - the next row to be processed immediately follows the last read row
|
151 |
+
*
|
152 |
+
* @return bool Whether we need data for the next row to be processed.
|
153 |
+
*/
|
154 |
+
protected function doesNeedDataForNextRowToBeProcessed()
|
155 |
+
{
|
156 |
+
$hasReadAtLeastOneRow = ($this->lastRowIndexProcessed !== 0);
|
157 |
+
|
158 |
+
return (
|
159 |
+
!$hasReadAtLeastOneRow ||
|
160 |
+
$this->lastRowIndexProcessed === $this->nextRowIndexToBeProcessed - 1
|
161 |
+
);
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* @return void
|
166 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
167 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
|
168 |
+
*/
|
169 |
+
protected function readDataForNextRow()
|
170 |
+
{
|
171 |
+
$this->currentlyProcessedRowData = [];
|
172 |
+
|
173 |
+
try {
|
174 |
+
$this->xmlProcessor->readUntilStopped();
|
175 |
+
} catch (XMLProcessingException $exception) {
|
176 |
+
throw new IOException("The sheet's data cannot be read. [{$exception->getMessage()}]");
|
177 |
+
}
|
178 |
+
|
179 |
+
$this->rowDataBuffer = $this->currentlyProcessedRowData;
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<table:table-row>" starting node
|
184 |
+
* @return int A return code that indicates what action should the processor take next
|
185 |
+
*/
|
186 |
+
protected function processRowStartingNode($xmlReader)
|
187 |
+
{
|
188 |
+
// Reset data from current row
|
189 |
+
$this->hasAlreadyReadOneCellInCurrentRow = false;
|
190 |
+
$this->lastProcessedCellValue = null;
|
191 |
+
$this->numColumnsRepeated = 1;
|
192 |
+
$this->numRowsRepeated = $this->getNumRowsRepeatedForCurrentNode($xmlReader);
|
193 |
+
|
194 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
195 |
+
}
|
196 |
+
|
197 |
+
/**
|
198 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<table:table-cell>" starting node
|
199 |
+
* @return int A return code that indicates what action should the processor take next
|
200 |
+
*/
|
201 |
+
protected function processCellStartingNode($xmlReader)
|
202 |
+
{
|
203 |
+
$currentNumColumnsRepeated = $this->getNumColumnsRepeatedForCurrentNode($xmlReader);
|
204 |
+
|
205 |
+
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
206 |
+
$node = $xmlReader->expand();
|
207 |
+
$currentCellValue = $this->getCellValue($node);
|
208 |
+
|
209 |
+
// process cell N only after having read cell N+1 (see below why)
|
210 |
+
if ($this->hasAlreadyReadOneCellInCurrentRow) {
|
211 |
+
for ($i = 0; $i < $this->numColumnsRepeated; $i++) {
|
212 |
+
$this->currentlyProcessedRowData[] = $this->lastProcessedCellValue;
|
213 |
+
}
|
214 |
+
}
|
215 |
+
|
216 |
+
$this->hasAlreadyReadOneCellInCurrentRow = true;
|
217 |
+
$this->lastProcessedCellValue = $currentCellValue;
|
218 |
+
$this->numColumnsRepeated = $currentNumColumnsRepeated;
|
219 |
+
|
220 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
221 |
+
}
|
222 |
+
|
223 |
+
/**
|
224 |
+
* @return int A return code that indicates what action should the processor take next
|
225 |
+
*/
|
226 |
+
protected function processRowEndingNode()
|
227 |
+
{
|
228 |
+
$isEmptyRow = $this->isEmptyRow($this->currentlyProcessedRowData, $this->lastProcessedCellValue);
|
229 |
+
|
230 |
+
// if the fetched row is empty and we don't want to preserve it...
|
231 |
+
if (!$this->shouldPreserveEmptyRows && $isEmptyRow) {
|
232 |
+
// ... skip it
|
233 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
234 |
+
}
|
235 |
+
|
236 |
+
// if the row is empty, we don't want to return more than one cell
|
237 |
+
$actualNumColumnsRepeated = (!$isEmptyRow) ? $this->numColumnsRepeated : 1;
|
238 |
+
|
239 |
+
// Only add the value if the last read cell is not a trailing empty cell repeater in Excel.
|
240 |
+
// The current count of read columns is determined by counting the values in "$this->currentlyProcessedRowData".
|
241 |
+
// This is to avoid creating a lot of empty cells, as Excel adds a last empty "<table:table-cell>"
|
242 |
+
// with a number-columns-repeated value equals to the number of (supported columns - used columns).
|
243 |
+
// In Excel, the number of supported columns is 16384, but we don't want to returns rows with
|
244 |
+
// always 16384 cells.
|
245 |
+
if ((count($this->currentlyProcessedRowData) + $actualNumColumnsRepeated) !== self::MAX_COLUMNS_EXCEL) {
|
246 |
+
for ($i = 0; $i < $actualNumColumnsRepeated; $i++) {
|
247 |
+
$this->currentlyProcessedRowData[] = $this->lastProcessedCellValue;
|
248 |
+
}
|
249 |
+
}
|
250 |
+
|
251 |
+
// If we are processing row N and the row is repeated M times,
|
252 |
+
// then the next row to be processed will be row (N+M).
|
253 |
+
$this->nextRowIndexToBeProcessed += $this->numRowsRepeated;
|
254 |
+
|
255 |
+
// at this point, we have all the data we need for the row
|
256 |
+
// so that we can populate the buffer
|
257 |
+
return XMLProcessor::PROCESSING_STOP;
|
258 |
+
}
|
259 |
+
|
260 |
+
/**
|
261 |
+
* @return int A return code that indicates what action should the processor take next
|
262 |
+
*/
|
263 |
+
protected function processTableEndingNode()
|
264 |
+
{
|
265 |
+
// The closing "</table:table>" marks the end of the file
|
266 |
+
$this->hasReachedEndOfFile = true;
|
267 |
+
|
268 |
+
return XMLProcessor::PROCESSING_STOP;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<table:table-row>" starting node
|
273 |
+
* @return int The value of "table:number-rows-repeated" attribute of the current node, or 1 if attribute missing
|
274 |
+
*/
|
275 |
+
protected function getNumRowsRepeatedForCurrentNode($xmlReader)
|
276 |
+
{
|
277 |
+
$numRowsRepeated = $xmlReader->getAttribute(self::XML_ATTRIBUTE_NUM_ROWS_REPEATED);
|
278 |
+
return ($numRowsRepeated !== null) ? intval($numRowsRepeated) : 1;
|
279 |
+
}
|
280 |
+
|
281 |
+
/**
|
282 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<table:table-cell>" starting node
|
283 |
+
* @return int The value of "table:number-columns-repeated" attribute of the current node, or 1 if attribute missing
|
284 |
+
*/
|
285 |
+
protected function getNumColumnsRepeatedForCurrentNode($xmlReader)
|
286 |
+
{
|
287 |
+
$numColumnsRepeated = $xmlReader->getAttribute(self::XML_ATTRIBUTE_NUM_COLUMNS_REPEATED);
|
288 |
+
return ($numColumnsRepeated !== null) ? intval($numColumnsRepeated) : 1;
|
289 |
+
}
|
290 |
+
|
291 |
+
/**
|
292 |
+
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
293 |
+
*
|
294 |
+
* @param \DOMNode $node
|
295 |
+
* @return string|int|float|bool|\DateTime|\DateInterval|null The value associated with the cell, empty string if cell's type is void/undefined, null on error
|
296 |
+
*/
|
297 |
+
protected function getCellValue($node)
|
298 |
+
{
|
299 |
+
return $this->cellValueFormatter->extractAndFormatNodeValue($node);
|
300 |
+
}
|
301 |
+
|
302 |
+
/**
|
303 |
+
* After finishing processing each cell, a row is considered empty if it contains
|
304 |
+
* no cells or if the value of the last read cell is an empty string.
|
305 |
+
* After finishing processing each cell, the last read cell is not part of the
|
306 |
+
* row data yet (as we still need to apply the "num-columns-repeated" attribute).
|
307 |
+
*
|
308 |
+
* @param array $rowData
|
309 |
+
* @param string|int|float|bool|\DateTime|\DateInterval|null The value of the last read cell
|
310 |
+
* @return bool Whether the row is empty
|
311 |
+
*/
|
312 |
+
protected function isEmptyRow($rowData, $lastReadCellValue)
|
313 |
+
{
|
314 |
+
return (
|
315 |
+
count($rowData) === 0 &&
|
316 |
+
(!isset($lastReadCellValue) || trim($lastReadCellValue) === '')
|
317 |
+
);
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Return the current element, from the buffer.
|
322 |
+
* @link http://php.net/manual/en/iterator.current.php
|
323 |
+
*
|
324 |
+
* @return array|null
|
325 |
+
*/
|
326 |
+
public function current()
|
327 |
+
{
|
328 |
+
return $this->rowDataBuffer;
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Return the key of the current element
|
333 |
+
* @link http://php.net/manual/en/iterator.key.php
|
334 |
+
*
|
335 |
+
* @return int
|
336 |
+
*/
|
337 |
+
public function key()
|
338 |
+
{
|
339 |
+
return $this->lastRowIndexProcessed;
|
340 |
+
}
|
341 |
+
|
342 |
+
|
343 |
+
/**
|
344 |
+
* Cleans up what was created to iterate over the object.
|
345 |
+
*
|
346 |
+
* @return void
|
347 |
+
*/
|
348 |
+
public function end()
|
349 |
+
{
|
350 |
+
$this->xmlReader->close();
|
351 |
+
}
|
352 |
+
}
|
app/Services/Spout/Reader/ODS/Sheet.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\SheetInterface;
|
6 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class Sheet
|
10 |
+
* Represents a sheet within a ODS file
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\ODS
|
13 |
+
*/
|
14 |
+
class Sheet implements SheetInterface
|
15 |
+
{
|
16 |
+
/** @var \Box\Spout\Reader\ODS\RowIterator To iterate over sheet's rows */
|
17 |
+
protected $rowIterator;
|
18 |
+
|
19 |
+
/** @var int ID of the sheet */
|
20 |
+
protected $id;
|
21 |
+
|
22 |
+
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
23 |
+
protected $index;
|
24 |
+
|
25 |
+
/** @var string Name of the sheet */
|
26 |
+
protected $name;
|
27 |
+
|
28 |
+
/** @var bool Whether the sheet was the active one */
|
29 |
+
protected $isActive;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* @param XMLReader $xmlReader XML Reader, positioned on the "<table:table>" element
|
33 |
+
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
34 |
+
* @param string $sheetName Name of the sheet
|
35 |
+
* @param bool $isSheetActive Whether the sheet was defined as active
|
36 |
+
* @param \Box\Spout\Reader\ODS\ReaderOptions $options Reader's current options
|
37 |
+
*/
|
38 |
+
public function __construct($xmlReader, $sheetIndex, $sheetName, $isSheetActive, $options)
|
39 |
+
{
|
40 |
+
$this->rowIterator = new RowIterator($xmlReader, $options);
|
41 |
+
$this->index = $sheetIndex;
|
42 |
+
$this->name = $sheetName;
|
43 |
+
$this->isActive = $isSheetActive;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @api
|
48 |
+
* @return \Box\Spout\Reader\ODS\RowIterator
|
49 |
+
*/
|
50 |
+
public function getRowIterator()
|
51 |
+
{
|
52 |
+
return $this->rowIterator;
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @api
|
57 |
+
* @return int Index of the sheet, based on order in the workbook (zero-based)
|
58 |
+
*/
|
59 |
+
public function getIndex()
|
60 |
+
{
|
61 |
+
return $this->index;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* @api
|
66 |
+
* @return string Name of the sheet
|
67 |
+
*/
|
68 |
+
public function getName()
|
69 |
+
{
|
70 |
+
return $this->name;
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* @api
|
75 |
+
* @return bool Whether the sheet was defined as active
|
76 |
+
*/
|
77 |
+
public function isActive()
|
78 |
+
{
|
79 |
+
return $this->isActive;
|
80 |
+
}
|
81 |
+
}
|
app/Services/Spout/Reader/ODS/SheetIterator.php
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\ODS;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\Exception\XMLProcessingException;
|
7 |
+
use Box\Spout\Reader\IteratorInterface;
|
8 |
+
use Box\Spout\Reader\ODS\Helper\SettingsHelper;
|
9 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Class SheetIterator
|
13 |
+
* Iterate over ODS sheet.
|
14 |
+
*
|
15 |
+
* @package Box\Spout\Reader\ODS
|
16 |
+
*/
|
17 |
+
class SheetIterator implements IteratorInterface
|
18 |
+
{
|
19 |
+
const CONTENT_XML_FILE_PATH = 'content.xml';
|
20 |
+
|
21 |
+
/** Definition of XML nodes name and attribute used to parse sheet data */
|
22 |
+
const XML_NODE_TABLE = 'table:table';
|
23 |
+
const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
|
24 |
+
|
25 |
+
/** @var string $filePath Path of the file to be read */
|
26 |
+
protected $filePath;
|
27 |
+
|
28 |
+
/** @var \Box\Spout\Reader\ODS\ReaderOptions Reader's current options */
|
29 |
+
protected $options;
|
30 |
+
|
31 |
+
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
32 |
+
protected $xmlReader;
|
33 |
+
|
34 |
+
/** @var \Box\Spout\Common\Escaper\ODS Used to unescape XML data */
|
35 |
+
protected $escaper;
|
36 |
+
|
37 |
+
/** @var bool Whether there are still at least a sheet to be read */
|
38 |
+
protected $hasFoundSheet;
|
39 |
+
|
40 |
+
/** @var int The index of the sheet being read (zero-based) */
|
41 |
+
protected $currentSheetIndex;
|
42 |
+
|
43 |
+
/** @var string The name of the sheet that was defined as active */
|
44 |
+
protected $activeSheetName;
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @param string $filePath Path of the file to be read
|
48 |
+
* @param \Box\Spout\Reader\ODS\ReaderOptions $options Reader's current options
|
49 |
+
* @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
|
50 |
+
*/
|
51 |
+
public function __construct($filePath, $options)
|
52 |
+
{
|
53 |
+
$this->filePath = $filePath;
|
54 |
+
$this->options = $options;
|
55 |
+
$this->xmlReader = new XMLReader();
|
56 |
+
|
57 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
58 |
+
$this->escaper = \Box\Spout\Common\Escaper\ODS::getInstance();
|
59 |
+
|
60 |
+
$settingsHelper = new SettingsHelper();
|
61 |
+
$this->activeSheetName = $settingsHelper->getActiveSheetName($filePath);
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Rewind the Iterator to the first element
|
66 |
+
* @link http://php.net/manual/en/iterator.rewind.php
|
67 |
+
*
|
68 |
+
* @return void
|
69 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the XML file containing sheets' data
|
70 |
+
*/
|
71 |
+
public function rewind()
|
72 |
+
{
|
73 |
+
$this->xmlReader->close();
|
74 |
+
|
75 |
+
if ($this->xmlReader->openFileInZip($this->filePath, self::CONTENT_XML_FILE_PATH) === false) {
|
76 |
+
$contentXmlFilePath = $this->filePath . '#' . self::CONTENT_XML_FILE_PATH;
|
77 |
+
throw new IOException("Could not open \"{$contentXmlFilePath}\".");
|
78 |
+
}
|
79 |
+
|
80 |
+
try {
|
81 |
+
$this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);
|
82 |
+
} catch (XMLProcessingException $exception) {
|
83 |
+
throw new IOException("The content.xml file is invalid and cannot be read. [{$exception->getMessage()}]");
|
84 |
+
}
|
85 |
+
|
86 |
+
$this->currentSheetIndex = 0;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Checks if current position is valid
|
91 |
+
* @link http://php.net/manual/en/iterator.valid.php
|
92 |
+
*
|
93 |
+
* @return bool
|
94 |
+
*/
|
95 |
+
public function valid()
|
96 |
+
{
|
97 |
+
return $this->hasFoundSheet;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Move forward to next element
|
102 |
+
* @link http://php.net/manual/en/iterator.next.php
|
103 |
+
*
|
104 |
+
* @return void
|
105 |
+
*/
|
106 |
+
public function next()
|
107 |
+
{
|
108 |
+
$this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);
|
109 |
+
|
110 |
+
if ($this->hasFoundSheet) {
|
111 |
+
$this->currentSheetIndex++;
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
/**
|
116 |
+
* Return the current element
|
117 |
+
* @link http://php.net/manual/en/iterator.current.php
|
118 |
+
*
|
119 |
+
* @return \Box\Spout\Reader\ODS\Sheet
|
120 |
+
*/
|
121 |
+
public function current()
|
122 |
+
{
|
123 |
+
$escapedSheetName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_NAME);
|
124 |
+
$sheetName = $this->escaper->unescape($escapedSheetName);
|
125 |
+
$isActiveSheet = $this->isActiveSheet($sheetName, $this->currentSheetIndex, $this->activeSheetName);
|
126 |
+
|
127 |
+
return new Sheet($this->xmlReader, $this->currentSheetIndex, $sheetName, $isActiveSheet, $this->options);
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Returns whether the current sheet was defined as the active one
|
132 |
+
*
|
133 |
+
* @param string $sheetName Name of the current sheet
|
134 |
+
* @param int $sheetIndex Index of the current sheet
|
135 |
+
* @param string|null Name of the sheet that was defined as active or NULL if none defined
|
136 |
+
* @return bool Whether the current sheet was defined as the active one
|
137 |
+
*/
|
138 |
+
private function isActiveSheet($sheetName, $sheetIndex, $activeSheetName)
|
139 |
+
{
|
140 |
+
// The given sheet is active if its name matches the defined active sheet's name
|
141 |
+
// or if no information about the active sheet was found, it defaults to the first sheet.
|
142 |
+
return (
|
143 |
+
($activeSheetName === null && $sheetIndex === 0) ||
|
144 |
+
($activeSheetName === $sheetName)
|
145 |
+
);
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Return the key of the current element
|
150 |
+
* @link http://php.net/manual/en/iterator.key.php
|
151 |
+
*
|
152 |
+
* @return int
|
153 |
+
*/
|
154 |
+
public function key()
|
155 |
+
{
|
156 |
+
return $this->currentSheetIndex + 1;
|
157 |
+
}
|
158 |
+
|
159 |
+
/**
|
160 |
+
* Cleans up what was created to iterate over the object.
|
161 |
+
*
|
162 |
+
* @return void
|
163 |
+
*/
|
164 |
+
public function end()
|
165 |
+
{
|
166 |
+
$this->xmlReader->close();
|
167 |
+
}
|
168 |
+
}
|
app/Services/Spout/Reader/ReaderFactory.php
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\UnsupportedTypeException;
|
6 |
+
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
|
7 |
+
use Box\Spout\Common\Type;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class ReaderFactory
|
11 |
+
* This factory is used to create readers, based on the type of the file to be read.
|
12 |
+
* It supports CSV and XLSX formats.
|
13 |
+
*
|
14 |
+
* @package Box\Spout\Reader
|
15 |
+
*/
|
16 |
+
class ReaderFactory
|
17 |
+
{
|
18 |
+
/**
|
19 |
+
* This creates an instance of the appropriate reader, given the type of the file to be read
|
20 |
+
*
|
21 |
+
* @api
|
22 |
+
* @param string $readerType Type of the reader to instantiate
|
23 |
+
* @return ReaderInterface
|
24 |
+
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
|
25 |
+
*/
|
26 |
+
public static function create($readerType)
|
27 |
+
{
|
28 |
+
$reader = null;
|
29 |
+
|
30 |
+
switch ($readerType) {
|
31 |
+
case Type::CSV:
|
32 |
+
$reader = new CSV\Reader();
|
33 |
+
break;
|
34 |
+
case Type::XLSX:
|
35 |
+
$reader = new XLSX\Reader();
|
36 |
+
break;
|
37 |
+
case Type::ODS:
|
38 |
+
$reader = new ODS\Reader();
|
39 |
+
break;
|
40 |
+
default:
|
41 |
+
throw new UnsupportedTypeException('No readers supporting the given type: ' . $readerType);
|
42 |
+
}
|
43 |
+
|
44 |
+
$reader->setGlobalFunctionsHelper(new GlobalFunctionsHelper());
|
45 |
+
|
46 |
+
return $reader;
|
47 |
+
}
|
48 |
+
}
|
app/Services/Spout/Reader/ReaderInterface.php
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface ReaderInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Reader
|
9 |
+
*/
|
10 |
+
interface ReaderInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Prepares the reader to read the given file. It also makes sure
|
14 |
+
* that the file exists and is readable.
|
15 |
+
*
|
16 |
+
* @param string $filePath Path of the file to be read
|
17 |
+
* @return void
|
18 |
+
* @throws \Box\Spout\Common\Exception\IOException
|
19 |
+
*/
|
20 |
+
public function open($filePath);
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Returns an iterator to iterate over sheets.
|
24 |
+
*
|
25 |
+
* @return \Iterator To iterate over sheets
|
26 |
+
* @throws \Box\Spout\Reader\Exception\ReaderNotOpenedException If called before opening the reader
|
27 |
+
*/
|
28 |
+
public function getSheetIterator();
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Closes the reader, preventing any additional reading
|
32 |
+
*
|
33 |
+
* @return void
|
34 |
+
*/
|
35 |
+
public function close();
|
36 |
+
}
|
app/Services/Spout/Reader/SheetInterface.php
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface SheetInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Reader
|
9 |
+
*/
|
10 |
+
interface SheetInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Returns an iterator to iterate over the sheet's rows.
|
14 |
+
*
|
15 |
+
* @return \Iterator
|
16 |
+
*/
|
17 |
+
public function getRowIterator();
|
18 |
+
}
|
app/Services/Spout/Reader/Wrapper/XMLInternalErrorsHelper.php
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Wrapper;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\Exception\XMLProcessingException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Trait XMLInternalErrorsHelper
|
9 |
+
*
|
10 |
+
* @package Box\Spout\Reader\Wrapper
|
11 |
+
*/
|
12 |
+
trait XMLInternalErrorsHelper
|
13 |
+
{
|
14 |
+
/** @var bool Stores whether XML errors were initially stored internally - used to reset */
|
15 |
+
protected $initialUseInternalErrorsValue;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* To avoid displaying lots of warning/error messages on screen,
|
19 |
+
* stores errors internally instead.
|
20 |
+
*
|
21 |
+
* @return void
|
22 |
+
*/
|
23 |
+
protected function useXMLInternalErrors()
|
24 |
+
{
|
25 |
+
libxml_clear_errors();
|
26 |
+
$this->initialUseInternalErrorsValue = libxml_use_internal_errors(true);
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Throws an XMLProcessingException if an error occured.
|
31 |
+
* It also always resets the "libxml_use_internal_errors" setting back to its initial value.
|
32 |
+
*
|
33 |
+
* @return void
|
34 |
+
* @throws \Box\Spout\Reader\Exception\XMLProcessingException
|
35 |
+
*/
|
36 |
+
protected function resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured()
|
37 |
+
{
|
38 |
+
if ($this->hasXMLErrorOccured()) {
|
39 |
+
$this->resetXMLInternalErrorsSetting();
|
40 |
+
throw new XMLProcessingException($this->getLastXMLErrorMessage());
|
41 |
+
}
|
42 |
+
|
43 |
+
$this->resetXMLInternalErrorsSetting();
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Returns whether the a XML error has occured since the last time errors were cleared.
|
48 |
+
*
|
49 |
+
* @return bool TRUE if an error occured, FALSE otherwise
|
50 |
+
*/
|
51 |
+
private function hasXMLErrorOccured()
|
52 |
+
{
|
53 |
+
return (libxml_get_last_error() !== false);
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Returns the error message for the last XML error that occured.
|
58 |
+
* @see libxml_get_last_error
|
59 |
+
*
|
60 |
+
* @return String|null Last XML error message or null if no error
|
61 |
+
*/
|
62 |
+
private function getLastXMLErrorMessage()
|
63 |
+
{
|
64 |
+
$errorMessage = null;
|
65 |
+
$error = libxml_get_last_error();
|
66 |
+
|
67 |
+
if ($error !== false) {
|
68 |
+
$errorMessage = trim($error->message);
|
69 |
+
}
|
70 |
+
|
71 |
+
return $errorMessage;
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* @return void
|
76 |
+
*/
|
77 |
+
protected function resetXMLInternalErrorsSetting()
|
78 |
+
{
|
79 |
+
libxml_use_internal_errors($this->initialUseInternalErrorsValue);
|
80 |
+
}
|
81 |
+
|
82 |
+
}
|
app/Services/Spout/Reader/Wrapper/XMLReader.php
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\Wrapper;
|
4 |
+
use DOMNode;
|
5 |
+
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class XMLReader
|
9 |
+
* Wrapper around the built-in XMLReader
|
10 |
+
* @see \XMLReader
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\Wrapper
|
13 |
+
*/
|
14 |
+
class XMLReader extends \XMLReader
|
15 |
+
{
|
16 |
+
use XMLInternalErrorsHelper;
|
17 |
+
|
18 |
+
const ZIP_WRAPPER = 'zip://';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Opens the XML Reader to read a file located inside a ZIP file.
|
22 |
+
*
|
23 |
+
* @param string $zipFilePath Path to the ZIP file
|
24 |
+
* @param string $fileInsideZipPath Relative or absolute path of the file inside the zip
|
25 |
+
* @return bool TRUE on success or FALSE on failure
|
26 |
+
*/
|
27 |
+
public function openFileInZip($zipFilePath, $fileInsideZipPath)
|
28 |
+
{
|
29 |
+
$wasOpenSuccessful = false;
|
30 |
+
$realPathURI = $this->getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath);
|
31 |
+
|
32 |
+
// We need to check first that the file we are trying to read really exist because:
|
33 |
+
// - PHP emits a warning when trying to open a file that does not exist.
|
34 |
+
// - HHVM does not check if file exists within zip file (@link https://github.com/facebook/hhvm/issues/5779)
|
35 |
+
if ($this->fileExistsWithinZip($realPathURI)) {
|
36 |
+
$wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
|
37 |
+
}
|
38 |
+
|
39 |
+
return $wasOpenSuccessful;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Returns the real path for the given path components.
|
44 |
+
* This is useful to avoid issues on some Windows setup.
|
45 |
+
*
|
46 |
+
* @param string $zipFilePath Path to the ZIP file
|
47 |
+
* @param string $fileInsideZipPath Relative or absolute path of the file inside the zip
|
48 |
+
* @return string The real path URI
|
49 |
+
*/
|
50 |
+
public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
|
51 |
+
{
|
52 |
+
return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPath);
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Returns whether the file at the given location exists
|
57 |
+
*
|
58 |
+
* @param string $zipStreamURI URI of a zip stream, e.g. "zip://file.zip#path/inside.xml"
|
59 |
+
* @return bool TRUE if the file exists, FALSE otherwise
|
60 |
+
*/
|
61 |
+
protected function fileExistsWithinZip($zipStreamURI)
|
62 |
+
{
|
63 |
+
$doesFileExists = false;
|
64 |
+
|
65 |
+
$pattern = '/zip:\/\/([^#]+)#(.*)/';
|
66 |
+
if (preg_match($pattern, $zipStreamURI, $matches)) {
|
67 |
+
$zipFilePath = $matches[1];
|
68 |
+
$innerFilePath = $matches[2];
|
69 |
+
|
70 |
+
$zip = new \ZipArchive();
|
71 |
+
if ($zip->open($zipFilePath) === true) {
|
72 |
+
$doesFileExists = ($zip->locateName($innerFilePath) !== false);
|
73 |
+
$zip->close();
|
74 |
+
}
|
75 |
+
}
|
76 |
+
|
77 |
+
return $doesFileExists;
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Move to next node in document
|
82 |
+
* @see \XMLReader::read
|
83 |
+
*
|
84 |
+
* @return bool TRUE on success or FALSE on failure
|
85 |
+
* @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
|
86 |
+
*/
|
87 |
+
public function read()
|
88 |
+
{
|
89 |
+
$this->useXMLInternalErrors();
|
90 |
+
|
91 |
+
$wasReadSuccessful = parent::read();
|
92 |
+
|
93 |
+
$this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
|
94 |
+
|
95 |
+
return $wasReadSuccessful;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Read until the element with the given name is found, or the end of the file.
|
100 |
+
*
|
101 |
+
* @param string $nodeName Name of the node to find
|
102 |
+
* @return bool TRUE on success or FALSE on failure
|
103 |
+
* @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
|
104 |
+
*/
|
105 |
+
public function readUntilNodeFound($nodeName)
|
106 |
+
{
|
107 |
+
do {
|
108 |
+
$wasReadSuccessful = $this->read();
|
109 |
+
$isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName);
|
110 |
+
} while ($wasReadSuccessful && $isNotPositionedOnStartingNode);
|
111 |
+
|
112 |
+
return $wasReadSuccessful;
|
113 |
+
}
|
114 |
+
|
115 |
+
/**
|
116 |
+
* Move cursor to next node skipping all subtrees
|
117 |
+
* @see \XMLReader::next
|
118 |
+
*
|
119 |
+
* @param string|void $localName The name of the next node to move to
|
120 |
+
* @return bool TRUE on success or FALSE on failure
|
121 |
+
* @throws \Box\Spout\Reader\Exception\XMLProcessingException If an error/warning occurred
|
122 |
+
*/
|
123 |
+
public function next($localName = null)
|
124 |
+
{
|
125 |
+
$this->useXMLInternalErrors();
|
126 |
+
|
127 |
+
$wasNextSuccessful = parent::next($localName);
|
128 |
+
|
129 |
+
$this->resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured();
|
130 |
+
|
131 |
+
return $wasNextSuccessful;
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* @param string $nodeName
|
136 |
+
* @return bool Whether the XML Reader is currently positioned on the starting node with given name
|
137 |
+
*/
|
138 |
+
public function isPositionedOnStartingNode($nodeName)
|
139 |
+
{
|
140 |
+
return $this->isPositionedOnNode($nodeName, XMLReader::ELEMENT);
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* @param string $nodeName
|
145 |
+
* @return bool Whether the XML Reader is currently positioned on the ending node with given name
|
146 |
+
*/
|
147 |
+
public function isPositionedOnEndingNode($nodeName)
|
148 |
+
{
|
149 |
+
return $this->isPositionedOnNode($nodeName, XMLReader::END_ELEMENT);
|
150 |
+
}
|
151 |
+
|
152 |
+
/**
|
153 |
+
* @param string $nodeName
|
154 |
+
* @param int $nodeType
|
155 |
+
* @return bool Whether the XML Reader is currently positioned on the node with given name and type
|
156 |
+
*/
|
157 |
+
private function isPositionedOnNode($nodeName, $nodeType)
|
158 |
+
{
|
159 |
+
// In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
|
160 |
+
// So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
|
161 |
+
// @see https://github.com/box/spout/issues/233
|
162 |
+
$hasPrefix = (strpos($nodeName, ':') !== false);
|
163 |
+
$currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
|
164 |
+
|
165 |
+
return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
|
166 |
+
}
|
167 |
+
|
168 |
+
/**
|
169 |
+
* @return string The name of the current node, un-prefixed
|
170 |
+
*/
|
171 |
+
public function getCurrentNodeName()
|
172 |
+
{
|
173 |
+
return $this->localName;
|
174 |
+
}
|
175 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/CellHelper.php
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\InvalidArgumentException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class CellHelper
|
9 |
+
* This class provides helper functions when working with cells
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader\XLSX\Helper
|
12 |
+
*/
|
13 |
+
class CellHelper
|
14 |
+
{
|
15 |
+
// Using ord() is super slow... Using a pre-computed hash table instead.
|
16 |
+
private static $columnLetterToIndexMapping = [
|
17 |
+
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
|
18 |
+
'H' => 7, 'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
|
19 |
+
'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
|
20 |
+
'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25,
|
21 |
+
];
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Fills the missing indexes of an array with a given value.
|
25 |
+
* For instance, $dataArray = []; $a[1] = 1; $a[3] = 3;
|
26 |
+
* Calling fillMissingArrayIndexes($dataArray, 'FILL') will return this array: ['FILL', 1, 'FILL', 3]
|
27 |
+
*
|
28 |
+
* @param array $dataArray The array to fill
|
29 |
+
* @param string|void $fillValue optional
|
30 |
+
* @return array
|
31 |
+
*/
|
32 |
+
public static function fillMissingArrayIndexes($dataArray, $fillValue = '')
|
33 |
+
{
|
34 |
+
if (empty($dataArray)) {
|
35 |
+
return [];
|
36 |
+
}
|
37 |
+
$existingIndexes = array_keys($dataArray);
|
38 |
+
|
39 |
+
$newIndexes = array_fill_keys(range(0, max($existingIndexes)), $fillValue);
|
40 |
+
$dataArray += $newIndexes;
|
41 |
+
|
42 |
+
ksort($dataArray);
|
43 |
+
|
44 |
+
return $dataArray;
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Returns the base 10 column index associated to the cell index (base 26).
|
49 |
+
* Excel uses A to Z letters for column indexing, where A is the 1st column,
|
50 |
+
* Z is the 26th and AA is the 27th.
|
51 |
+
* The mapping is zero based, so that A1 maps to 0, B2 maps to 1, Z13 to 25 and AA4 to 26.
|
52 |
+
*
|
53 |
+
* @param string $cellIndex The Excel cell index ('A1', 'BC13', ...)
|
54 |
+
* @return int
|
55 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException When the given cell index is invalid
|
56 |
+
*/
|
57 |
+
public static function getColumnIndexFromCellIndex($cellIndex)
|
58 |
+
{
|
59 |
+
if (!self::isValidCellIndex($cellIndex)) {
|
60 |
+
throw new InvalidArgumentException('Cannot get column index from an invalid cell index.');
|
61 |
+
}
|
62 |
+
|
63 |
+
$columnIndex = 0;
|
64 |
+
|
65 |
+
// Remove row information
|
66 |
+
$columnLetters = preg_replace('/\d/', '', $cellIndex);
|
67 |
+
|
68 |
+
// strlen() is super slow too... Using isset() is way faster and not too unreadable,
|
69 |
+
// since we checked before that there are between 1 and 3 letters.
|
70 |
+
$columnLength = isset($columnLetters[1]) ? (isset($columnLetters[2]) ? 3 : 2) : 1;
|
71 |
+
|
72 |
+
// Looping over the different letters of the column is slower than this method.
|
73 |
+
// Also, not using the pow() function because it's slooooow...
|
74 |
+
switch ($columnLength) {
|
75 |
+
case 1:
|
76 |
+
$columnIndex = (self::$columnLetterToIndexMapping[$columnLetters]);
|
77 |
+
break;
|
78 |
+
case 2:
|
79 |
+
$firstLetterIndex = (self::$columnLetterToIndexMapping[$columnLetters[0]] + 1) * 26;
|
80 |
+
$secondLetterIndex = self::$columnLetterToIndexMapping[$columnLetters[1]];
|
81 |
+
$columnIndex = $firstLetterIndex + $secondLetterIndex;
|
82 |
+
break;
|
83 |
+
case 3:
|
84 |
+
$firstLetterIndex = (self::$columnLetterToIndexMapping[$columnLetters[0]] + 1) * 676;
|
85 |
+
$secondLetterIndex = (self::$columnLetterToIndexMapping[$columnLetters[1]] + 1) * 26;
|
86 |
+
$thirdLetterIndex = self::$columnLetterToIndexMapping[$columnLetters[2]];
|
87 |
+
$columnIndex = $firstLetterIndex + $secondLetterIndex + $thirdLetterIndex;
|
88 |
+
break;
|
89 |
+
}
|
90 |
+
|
91 |
+
return $columnIndex;
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Returns whether a cell index is valid, in an Excel world.
|
96 |
+
* To be valid, the cell index should start with capital letters and be followed by numbers.
|
97 |
+
* There can only be 3 letters, as there can only be 16,384 rows, which is equivalent to 'XFE'.
|
98 |
+
*
|
99 |
+
* @param string $cellIndex The Excel cell index ('A1', 'BC13', ...)
|
100 |
+
* @return bool
|
101 |
+
*/
|
102 |
+
protected static function isValidCellIndex($cellIndex)
|
103 |
+
{
|
104 |
+
return (preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1);
|
105 |
+
}
|
106 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/CellValueFormatter.php
ADDED
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class CellValueFormatter
|
7 |
+
* This class provides helper functions to format cell values
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Reader\XLSX\Helper
|
10 |
+
*/
|
11 |
+
class CellValueFormatter
|
12 |
+
{
|
13 |
+
/** Definition of all possible cell types */
|
14 |
+
const CELL_TYPE_INLINE_STRING = 'inlineStr';
|
15 |
+
const CELL_TYPE_STR = 'str';
|
16 |
+
const CELL_TYPE_SHARED_STRING = 's';
|
17 |
+
const CELL_TYPE_BOOLEAN = 'b';
|
18 |
+
const CELL_TYPE_NUMERIC = 'n';
|
19 |
+
const CELL_TYPE_DATE = 'd';
|
20 |
+
const CELL_TYPE_ERROR = 'e';
|
21 |
+
|
22 |
+
/** Definition of XML nodes names used to parse data */
|
23 |
+
const XML_NODE_VALUE = 'v';
|
24 |
+
const XML_NODE_INLINE_STRING_VALUE = 't';
|
25 |
+
|
26 |
+
/** Definition of XML attributes used to parse data */
|
27 |
+
const XML_ATTRIBUTE_TYPE = 't';
|
28 |
+
const XML_ATTRIBUTE_STYLE_ID = 's';
|
29 |
+
|
30 |
+
/** Constants used for date formatting */
|
31 |
+
const NUM_SECONDS_IN_ONE_DAY = 86400;
|
32 |
+
const NUM_SECONDS_IN_ONE_HOUR = 3600;
|
33 |
+
const NUM_SECONDS_IN_ONE_MINUTE = 60;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* February 29th, 1900 is NOT a leap year but Excel thinks it is...
|
37 |
+
* @see https://en.wikipedia.org/wiki/Year_1900_problem#Microsoft_Excel
|
38 |
+
*/
|
39 |
+
const ERRONEOUS_EXCEL_LEAP_YEAR_DAY = 60;
|
40 |
+
|
41 |
+
/** @var SharedStringsHelper Helper to work with shared strings */
|
42 |
+
protected $sharedStringsHelper;
|
43 |
+
|
44 |
+
/** @var StyleHelper Helper to work with styles */
|
45 |
+
protected $styleHelper;
|
46 |
+
|
47 |
+
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
48 |
+
protected $shouldFormatDates;
|
49 |
+
|
50 |
+
/** @var \Box\Spout\Common\Escaper\XLSX Used to unescape XML data */
|
51 |
+
protected $escaper;
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @param SharedStringsHelper $sharedStringsHelper Helper to work with shared strings
|
55 |
+
* @param StyleHelper $styleHelper Helper to work with styles
|
56 |
+
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
57 |
+
*/
|
58 |
+
public function __construct($sharedStringsHelper, $styleHelper, $shouldFormatDates)
|
59 |
+
{
|
60 |
+
$this->sharedStringsHelper = $sharedStringsHelper;
|
61 |
+
$this->styleHelper = $styleHelper;
|
62 |
+
$this->shouldFormatDates = $shouldFormatDates;
|
63 |
+
|
64 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
65 |
+
$this->escaper = \Box\Spout\Common\Escaper\XLSX::getInstance();
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
70 |
+
*
|
71 |
+
* @param \DOMNode $node
|
72 |
+
* @return string|int|float|bool|\DateTime|null The value associated with the cell (null when the cell has an error)
|
73 |
+
*/
|
74 |
+
public function extractAndFormatNodeValue($node)
|
75 |
+
{
|
76 |
+
// Default cell type is "n"
|
77 |
+
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC;
|
78 |
+
$cellStyleId = intval($node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID));
|
79 |
+
$vNodeValue = $this->getVNodeValue($node);
|
80 |
+
|
81 |
+
if (($vNodeValue === '') && ($cellType !== self::CELL_TYPE_INLINE_STRING)) {
|
82 |
+
return $vNodeValue;
|
83 |
+
}
|
84 |
+
|
85 |
+
switch ($cellType) {
|
86 |
+
case self::CELL_TYPE_INLINE_STRING:
|
87 |
+
return $this->formatInlineStringCellValue($node);
|
88 |
+
case self::CELL_TYPE_SHARED_STRING:
|
89 |
+
return $this->formatSharedStringCellValue($vNodeValue);
|
90 |
+
case self::CELL_TYPE_STR:
|
91 |
+
return $this->formatStrCellValue($vNodeValue);
|
92 |
+
case self::CELL_TYPE_BOOLEAN:
|
93 |
+
return $this->formatBooleanCellValue($vNodeValue);
|
94 |
+
case self::CELL_TYPE_NUMERIC:
|
95 |
+
return $this->formatNumericCellValue($vNodeValue, $cellStyleId);
|
96 |
+
case self::CELL_TYPE_DATE:
|
97 |
+
return $this->formatDateCellValue($vNodeValue);
|
98 |
+
default:
|
99 |
+
return null;
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Returns the cell's string value from a node's nested value node
|
105 |
+
*
|
106 |
+
* @param \DOMNode $node
|
107 |
+
* @return string The value associated with the cell
|
108 |
+
*/
|
109 |
+
protected function getVNodeValue($node)
|
110 |
+
{
|
111 |
+
// for cell types having a "v" tag containing the value.
|
112 |
+
// if not, the returned value should be empty string.
|
113 |
+
$vNode = $node->getElementsByTagName(self::XML_NODE_VALUE)->item(0);
|
114 |
+
return ($vNode !== null) ? $vNode->nodeValue : '';
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Returns the cell String value where string is inline.
|
119 |
+
*
|
120 |
+
* @param \DOMNode $node
|
121 |
+
* @return string The value associated with the cell (null when the cell has an error)
|
122 |
+
*/
|
123 |
+
protected function formatInlineStringCellValue($node)
|
124 |
+
{
|
125 |
+
// inline strings are formatted this way:
|
126 |
+
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
127 |
+
$tNode = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE)->item(0);
|
128 |
+
$cellValue = $this->escaper->unescape($tNode->nodeValue);
|
129 |
+
return $cellValue;
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Returns the cell String value from shared-strings file using nodeValue index.
|
134 |
+
*
|
135 |
+
* @param string $nodeValue
|
136 |
+
* @return string The value associated with the cell (null when the cell has an error)
|
137 |
+
*/
|
138 |
+
protected function formatSharedStringCellValue($nodeValue)
|
139 |
+
{
|
140 |
+
// shared strings are formatted this way:
|
141 |
+
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
|
142 |
+
$sharedStringIndex = intval($nodeValue);
|
143 |
+
$escapedCellValue = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
|
144 |
+
$cellValue = $this->escaper->unescape($escapedCellValue);
|
145 |
+
return $cellValue;
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Returns the cell String value, where string is stored in value node.
|
150 |
+
*
|
151 |
+
* @param string $nodeValue
|
152 |
+
* @return string The value associated with the cell (null when the cell has an error)
|
153 |
+
*/
|
154 |
+
protected function formatStrCellValue($nodeValue)
|
155 |
+
{
|
156 |
+
$escapedCellValue = trim($nodeValue);
|
157 |
+
$cellValue = $this->escaper->unescape($escapedCellValue);
|
158 |
+
return $cellValue;
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Returns the cell Numeric value from string of nodeValue.
|
163 |
+
* The value can also represent a timestamp and a DateTime will be returned.
|
164 |
+
*
|
165 |
+
* @param string $nodeValue
|
166 |
+
* @param int $cellStyleId 0 being the default style
|
167 |
+
* @return int|float|\DateTime|null The value associated with the cell
|
168 |
+
*/
|
169 |
+
protected function formatNumericCellValue($nodeValue, $cellStyleId)
|
170 |
+
{
|
171 |
+
// Numeric values can represent numbers as well as timestamps.
|
172 |
+
// We need to look at the style of the cell to determine whether it is one or the other.
|
173 |
+
$shouldFormatAsDate = $this->styleHelper->shouldFormatNumericValueAsDate($cellStyleId);
|
174 |
+
|
175 |
+
if ($shouldFormatAsDate) {
|
176 |
+
return $this->formatExcelTimestampValue(floatval($nodeValue), $cellStyleId);
|
177 |
+
} else {
|
178 |
+
$nodeIntValue = intval($nodeValue);
|
179 |
+
return ($nodeIntValue == $nodeValue) ? $nodeIntValue : floatval($nodeValue);
|
180 |
+
}
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Returns a cell's PHP Date value, associated to the given timestamp.
|
185 |
+
* NOTE: The timestamp is a float representing the number of days since January 1st, 1900.
|
186 |
+
* NOTE: The timestamp can also represent a time, if it is a value between 0 and 1.
|
187 |
+
*
|
188 |
+
* @param float $nodeValue
|
189 |
+
* @param int $cellStyleId 0 being the default style
|
190 |
+
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
|
191 |
+
*/
|
192 |
+
protected function formatExcelTimestampValue($nodeValue, $cellStyleId)
|
193 |
+
{
|
194 |
+
// Fix for the erroneous leap year in Excel
|
195 |
+
if (ceil($nodeValue) > self::ERRONEOUS_EXCEL_LEAP_YEAR_DAY) {
|
196 |
+
--$nodeValue;
|
197 |
+
}
|
198 |
+
|
199 |
+
if ($nodeValue >= 1) {
|
200 |
+
// Values greater than 1 represent "dates". The value 1.0 representing the "base" date: 1900-01-01.
|
201 |
+
return $this->formatExcelTimestampValueAsDateValue($nodeValue, $cellStyleId);
|
202 |
+
} else if ($nodeValue >= 0) {
|
203 |
+
// Values between 0 and 1 represent "times".
|
204 |
+
return $this->formatExcelTimestampValueAsTimeValue($nodeValue, $cellStyleId);
|
205 |
+
} else {
|
206 |
+
// invalid date
|
207 |
+
return null;
|
208 |
+
}
|
209 |
+
}
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Returns a cell's PHP DateTime value, associated to the given timestamp.
|
213 |
+
* Only the time value matters. The date part is set to Jan 1st, 1900 (base Excel date).
|
214 |
+
*
|
215 |
+
* @param float $nodeValue
|
216 |
+
* @param int $cellStyleId 0 being the default style
|
217 |
+
* @return \DateTime|string The value associated with the cell
|
218 |
+
*/
|
219 |
+
protected function formatExcelTimestampValueAsTimeValue($nodeValue, $cellStyleId)
|
220 |
+
{
|
221 |
+
$time = round($nodeValue * self::NUM_SECONDS_IN_ONE_DAY);
|
222 |
+
$hours = floor($time / self::NUM_SECONDS_IN_ONE_HOUR);
|
223 |
+
$minutes = floor($time / self::NUM_SECONDS_IN_ONE_MINUTE) - ($hours * self::NUM_SECONDS_IN_ONE_MINUTE);
|
224 |
+
$seconds = $time - ($hours * self::NUM_SECONDS_IN_ONE_HOUR) - ($minutes * self::NUM_SECONDS_IN_ONE_MINUTE);
|
225 |
+
|
226 |
+
// using the base Excel date (Jan 1st, 1900) - not relevant here
|
227 |
+
$dateObj = new \DateTime('1900-01-01');
|
228 |
+
$dateObj->setTime($hours, $minutes, $seconds);
|
229 |
+
|
230 |
+
if ($this->shouldFormatDates) {
|
231 |
+
$styleNumberFormatCode = $this->styleHelper->getNumberFormatCode($cellStyleId);
|
232 |
+
$phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
|
233 |
+
return $dateObj->format($phpDateFormat);
|
234 |
+
} else {
|
235 |
+
return $dateObj;
|
236 |
+
}
|
237 |
+
}
|
238 |
+
|
239 |
+
/**
|
240 |
+
* Returns a cell's PHP Date value, associated to the given timestamp.
|
241 |
+
* NOTE: The timestamp is a float representing the number of days since January 1st, 1900.
|
242 |
+
*
|
243 |
+
* @param float $nodeValue
|
244 |
+
* @param int $cellStyleId 0 being the default style
|
245 |
+
* @return \DateTime|string|null The value associated with the cell or NULL if invalid date value
|
246 |
+
*/
|
247 |
+
protected function formatExcelTimestampValueAsDateValue($nodeValue, $cellStyleId)
|
248 |
+
{
|
249 |
+
// Do not use any unix timestamps for calculation to prevent
|
250 |
+
// issues with numbers exceeding 2^31.
|
251 |
+
$secondsRemainder = fmod($nodeValue, 1) * self::NUM_SECONDS_IN_ONE_DAY;
|
252 |
+
$secondsRemainder = round($secondsRemainder, 0);
|
253 |
+
|
254 |
+
try {
|
255 |
+
$dateObj = \DateTime::createFromFormat('|Y-m-d', '1899-12-31');
|
256 |
+
$dateObj->modify('+' . intval($nodeValue) . 'days');
|
257 |
+
$dateObj->modify('+' . $secondsRemainder . 'seconds');
|
258 |
+
|
259 |
+
if ($this->shouldFormatDates) {
|
260 |
+
$styleNumberFormatCode = $this->styleHelper->getNumberFormatCode($cellStyleId);
|
261 |
+
$phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
|
262 |
+
return $dateObj->format($phpDateFormat);
|
263 |
+
} else {
|
264 |
+
return $dateObj;
|
265 |
+
}
|
266 |
+
} catch (\Exception $e) {
|
267 |
+
return null;
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Returns the cell Boolean value from a specific node's Value.
|
273 |
+
*
|
274 |
+
* @param string $nodeValue
|
275 |
+
* @return bool The value associated with the cell
|
276 |
+
*/
|
277 |
+
protected function formatBooleanCellValue($nodeValue)
|
278 |
+
{
|
279 |
+
// !! is similar to boolval()
|
280 |
+
$cellValue = !!$nodeValue;
|
281 |
+
return $cellValue;
|
282 |
+
}
|
283 |
+
|
284 |
+
/**
|
285 |
+
* Returns a cell's PHP Date value, associated to the given stored nodeValue.
|
286 |
+
* @see ECMA-376 Part 1 - §18.17.4
|
287 |
+
*
|
288 |
+
* @param string $nodeValue ISO 8601 Date string
|
289 |
+
* @return \DateTime|string|null The value associated with the cell or NULL if invalid date value
|
290 |
+
*/
|
291 |
+
protected function formatDateCellValue($nodeValue)
|
292 |
+
{
|
293 |
+
// Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
|
294 |
+
try {
|
295 |
+
return ($this->shouldFormatDates) ? $nodeValue : new \DateTime($nodeValue);
|
296 |
+
} catch (\Exception $e) {
|
297 |
+
return null;
|
298 |
+
}
|
299 |
+
}
|
300 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/DateFormatHelper.php
ADDED
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class DateFormatHelper
|
7 |
+
* This class provides helper functions to format Excel dates
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Reader\XLSX\Helper
|
10 |
+
*/
|
11 |
+
class DateFormatHelper
|
12 |
+
{
|
13 |
+
const KEY_GENERAL = 'general';
|
14 |
+
const KEY_HOUR_12 = '12h';
|
15 |
+
const KEY_HOUR_24 = '24h';
|
16 |
+
|
17 |
+
/**
|
18 |
+
* This map is used to replace Excel format characters by their PHP equivalent.
|
19 |
+
* Keys should be ordered from longest to smallest.
|
20 |
+
*
|
21 |
+
* @var array Mapping between Excel format characters and PHP format characters
|
22 |
+
*/
|
23 |
+
private static $excelDateFormatToPHPDateFormatMapping = [
|
24 |
+
self::KEY_GENERAL => [
|
25 |
+
// Time
|
26 |
+
'am/pm' => 'A', // Uppercase Ante meridiem and Post meridiem
|
27 |
+
':mm' => ':i', // Minutes with leading zeros - if preceded by a ":" (otherwise month)
|
28 |
+
'mm:' => 'i:', // Minutes with leading zeros - if followed by a ":" (otherwise month)
|
29 |
+
'ss' => 's', // Seconds, with leading zeros
|
30 |
+
'.s' => '', // Ignore (fractional seconds format does not exist in PHP)
|
31 |
+
|
32 |
+
// Date
|
33 |
+
'e' => 'Y', // Full numeric representation of a year, 4 digits
|
34 |
+
'yyyy' => 'Y', // Full numeric representation of a year, 4 digits
|
35 |
+
'yy' => 'y', // Two digit representation of a year
|
36 |
+
'mmmmm' => 'M', // Short textual representation of a month, three letters ("mmmmm" should only contain the 1st letter...)
|
37 |
+
'mmmm' => 'F', // Full textual representation of a month
|
38 |
+
'mmm' => 'M', // Short textual representation of a month, three letters
|
39 |
+
'mm' => 'm', // Numeric representation of a month, with leading zeros
|
40 |
+
'm' => 'n', // Numeric representation of a month, without leading zeros
|
41 |
+
'dddd' => 'l', // Full textual representation of the day of the week
|
42 |
+
'ddd' => 'D', // Textual representation of a day, three letters
|
43 |
+
'dd' => 'd', // Day of the month, 2 digits with leading zeros
|
44 |
+
'd' => 'j', // Day of the month without leading zeros
|
45 |
+
],
|
46 |
+
self::KEY_HOUR_12 => [
|
47 |
+
'hh' => 'h', // 12-hour format of an hour without leading zeros
|
48 |
+
'h' => 'g', // 12-hour format of an hour without leading zeros
|
49 |
+
],
|
50 |
+
self::KEY_HOUR_24 => [
|
51 |
+
'hh' => 'H', // 24-hour hours with leading zero
|
52 |
+
'h' => 'G', // 24-hour format of an hour without leading zeros
|
53 |
+
],
|
54 |
+
];
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Converts the given Excel date format to a format understandable by the PHP date function.
|
58 |
+
*
|
59 |
+
* @param string $excelDateFormat Excel date format
|
60 |
+
* @return string PHP date format (as defined here: http://php.net/manual/en/function.date.php)
|
61 |
+
*/
|
62 |
+
public static function toPHPDateFormat($excelDateFormat)
|
63 |
+
{
|
64 |
+
// Remove brackets potentially present at the beginning of the format string
|
65 |
+
// and text portion of the format at the end of it (starting with ";")
|
66 |
+
// See §18.8.31 of ECMA-376 for more detail.
|
67 |
+
$dateFormat = preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat);
|
68 |
+
|
69 |
+
// Double quotes are used to escape characters that must not be interpreted.
|
70 |
+
// For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y"
|
71 |
+
// By exploding the format string using double quote as a delimiter, we can get all parts
|
72 |
+
// that must be transformed (even indexes) and all parts that must not be (odd indexes).
|
73 |
+
$dateFormatParts = explode('"', $dateFormat);
|
74 |
+
|
75 |
+
foreach ($dateFormatParts as $partIndex => $dateFormatPart) {
|
76 |
+
// do not look at odd indexes
|
77 |
+
if ($partIndex % 2 === 1) {
|
78 |
+
continue;
|
79 |
+
}
|
80 |
+
|
81 |
+
// Make sure all characters are lowercase, as the mapping table is using lowercase characters
|
82 |
+
$transformedPart = strtolower($dateFormatPart);
|
83 |
+
|
84 |
+
// Remove escapes related to non-format characters
|
85 |
+
$transformedPart = str_replace('\\', '', $transformedPart);
|
86 |
+
|
87 |
+
// Apply general transformation first...
|
88 |
+
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]);
|
89 |
+
|
90 |
+
// ... then apply hour transformation, for 12-hour or 24-hour format
|
91 |
+
if (self::has12HourFormatMarker($dateFormatPart)) {
|
92 |
+
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]);
|
93 |
+
} else {
|
94 |
+
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]);
|
95 |
+
}
|
96 |
+
|
97 |
+
// overwrite the parts array with the new transformed part
|
98 |
+
$dateFormatParts[$partIndex] = $transformedPart;
|
99 |
+
}
|
100 |
+
|
101 |
+
// Merge all transformed parts back together
|
102 |
+
$phpDateFormat = implode('"', $dateFormatParts);
|
103 |
+
|
104 |
+
// Finally, to have the date format compatible with the DateTime::format() function, we need to escape
|
105 |
+
// all characters that are inside double quotes (and double quotes must be removed).
|
106 |
+
// For instance, ["Day " dd] should become [\D\a\y\ dd]
|
107 |
+
$phpDateFormat = preg_replace_callback('/"(.+?)"/', function($matches) {
|
108 |
+
$stringToEscape = $matches[1];
|
109 |
+
$letters = preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY);
|
110 |
+
return '\\' . implode('\\', $letters);
|
111 |
+
}, $phpDateFormat);
|
112 |
+
|
113 |
+
return $phpDateFormat;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* @param string $excelDateFormat Date format as defined by Excel
|
118 |
+
* @return bool Whether the given date format has the 12-hour format marker
|
119 |
+
*/
|
120 |
+
private static function has12HourFormatMarker($excelDateFormat)
|
121 |
+
{
|
122 |
+
return (stripos($excelDateFormat, 'am/pm') !== false);
|
123 |
+
}
|
124 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/CachingStrategyFactory.php
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class CachingStrategyFactory
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
|
9 |
+
*/
|
10 |
+
class CachingStrategyFactory
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* The memory amount needed to store a string was obtained empirically from this data:
|
14 |
+
*
|
15 |
+
* ------------------------------------
|
16 |
+
* | Number of chars⁺ | Memory needed |
|
17 |
+
* ------------------------------------
|
18 |
+
* | 3,000 | 1 MB |
|
19 |
+
* | 15,000 | 2 MB |
|
20 |
+
* | 30,000 | 5 MB |
|
21 |
+
* | 75,000 | 11 MB |
|
22 |
+
* | 150,000 | 21 MB |
|
23 |
+
* | 300,000 | 43 MB |
|
24 |
+
* | 750,000 | 105 MB |
|
25 |
+
* | 1,500,000 | 210 MB |
|
26 |
+
* | 2,250,000 | 315 MB |
|
27 |
+
* | 3,000,000 | 420 MB |
|
28 |
+
* | 4,500,000 | 630 MB |
|
29 |
+
* ------------------------------------
|
30 |
+
*
|
31 |
+
* ⁺ All characters were 1 byte long
|
32 |
+
*
|
33 |
+
* This gives a linear graph where each 1-byte character requires about 150 bytes to be stored.
|
34 |
+
* Given that some characters can take up to 4 bytes, we need 600 bytes per character to be safe.
|
35 |
+
* Also, there is on average about 20 characters per cell (this is entirely empirical data...).
|
36 |
+
*
|
37 |
+
* This means that in order to store one shared string in memory, the memory amount needed is:
|
38 |
+
* => 20 * 600 ≈ 12KB
|
39 |
+
*/
|
40 |
+
const AMOUNT_MEMORY_NEEDED_PER_STRING_IN_KB = 12;
|
41 |
+
|
42 |
+
/**
|
43 |
+
* To avoid running out of memory when extracting a huge number of shared strings, they can be saved to temporary files
|
44 |
+
* instead of in memory. Then, when accessing a string, the corresponding file contents will be loaded in memory
|
45 |
+
* and the string will be quickly retrieved.
|
46 |
+
* The performance bottleneck is not when creating these temporary files, but rather when loading their content.
|
47 |
+
* Because the contents of the last loaded file stays in memory until another file needs to be loaded, it works
|
48 |
+
* best when the indexes of the shared strings are sorted in the sheet data.
|
49 |
+
* 10,000 was chosen because it creates small files that are fast to be loaded in memory.
|
50 |
+
*/
|
51 |
+
const MAX_NUM_STRINGS_PER_TEMP_FILE = 10000;
|
52 |
+
|
53 |
+
/** @var CachingStrategyFactory|null Singleton instance */
|
54 |
+
protected static $instance = null;
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Private constructor for singleton
|
58 |
+
*/
|
59 |
+
private function __construct()
|
60 |
+
{
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Returns the singleton instance of the factory
|
65 |
+
*
|
66 |
+
* @return CachingStrategyFactory
|
67 |
+
*/
|
68 |
+
public static function getInstance()
|
69 |
+
{
|
70 |
+
if (self::$instance === null) {
|
71 |
+
self::$instance = new CachingStrategyFactory();
|
72 |
+
}
|
73 |
+
|
74 |
+
return self::$instance;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Returns the best caching strategy, given the number of unique shared strings
|
79 |
+
* and the amount of memory available.
|
80 |
+
*
|
81 |
+
* @param int|null $sharedStringsUniqueCount Number of unique shared strings (NULL if unknown)
|
82 |
+
* @param string|void $tempFolder Temporary folder where the temporary files to store shared strings will be stored
|
83 |
+
* @return CachingStrategyInterface The best caching strategy
|
84 |
+
*/
|
85 |
+
public function getBestCachingStrategy($sharedStringsUniqueCount, $tempFolder = null)
|
86 |
+
{
|
87 |
+
if ($this->isInMemoryStrategyUsageSafe($sharedStringsUniqueCount)) {
|
88 |
+
return new InMemoryStrategy($sharedStringsUniqueCount);
|
89 |
+
} else {
|
90 |
+
return new FileBasedStrategy($tempFolder, self::MAX_NUM_STRINGS_PER_TEMP_FILE);
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Returns whether it is safe to use in-memory caching, given the number of unique shared strings
|
96 |
+
* and the amount of memory available.
|
97 |
+
*
|
98 |
+
* @param int|null $sharedStringsUniqueCount Number of unique shared strings (NULL if unknown)
|
99 |
+
* @return bool
|
100 |
+
*/
|
101 |
+
protected function isInMemoryStrategyUsageSafe($sharedStringsUniqueCount)
|
102 |
+
{
|
103 |
+
// if the number of shared strings in unknown, do not use "in memory" strategy
|
104 |
+
if ($sharedStringsUniqueCount === null) {
|
105 |
+
return false;
|
106 |
+
}
|
107 |
+
|
108 |
+
$memoryAvailable = $this->getMemoryLimitInKB();
|
109 |
+
|
110 |
+
if ($memoryAvailable === -1) {
|
111 |
+
// if cannot get memory limit or if memory limit set as unlimited, don't trust and play safe
|
112 |
+
return ($sharedStringsUniqueCount < self::MAX_NUM_STRINGS_PER_TEMP_FILE);
|
113 |
+
} else {
|
114 |
+
$memoryNeeded = $sharedStringsUniqueCount * self::AMOUNT_MEMORY_NEEDED_PER_STRING_IN_KB;
|
115 |
+
return ($memoryAvailable > $memoryNeeded);
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Returns the PHP "memory_limit" in Kilobytes
|
121 |
+
*
|
122 |
+
* @return float
|
123 |
+
*/
|
124 |
+
protected function getMemoryLimitInKB()
|
125 |
+
{
|
126 |
+
$memoryLimitFormatted = $this->getMemoryLimitFromIni();
|
127 |
+
$memoryLimitFormatted = strtolower(trim($memoryLimitFormatted));
|
128 |
+
|
129 |
+
// No memory limit
|
130 |
+
if ($memoryLimitFormatted === '-1') {
|
131 |
+
return -1;
|
132 |
+
}
|
133 |
+
|
134 |
+
if (preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) {
|
135 |
+
$amount = intval($matches[1]);
|
136 |
+
$unit = $matches[2];
|
137 |
+
|
138 |
+
switch ($unit) {
|
139 |
+
case 'b': return ($amount / 1024);
|
140 |
+
case 'k': return $amount;
|
141 |
+
case 'm': return ($amount * 1024);
|
142 |
+
case 'g': return ($amount * 1024 * 1024);
|
143 |
+
case 't': return ($amount * 1024 * 1024 * 1024);
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
147 |
+
return -1;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Returns the formatted "memory_limit" value
|
152 |
+
*
|
153 |
+
* @return string
|
154 |
+
*/
|
155 |
+
protected function getMemoryLimitFromIni()
|
156 |
+
{
|
157 |
+
return ini_get('memory_limit');
|
158 |
+
}
|
159 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/CachingStrategyInterface.php
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface CachingStrategyInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
|
9 |
+
*/
|
10 |
+
interface CachingStrategyInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Adds the given string to the cache.
|
14 |
+
*
|
15 |
+
* @param string $sharedString The string to be added to the cache
|
16 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
17 |
+
* @return void
|
18 |
+
*/
|
19 |
+
public function addStringForIndex($sharedString, $sharedStringIndex);
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Closes the cache after the last shared string was added.
|
23 |
+
* This prevents any additional string from being added to the cache.
|
24 |
+
*
|
25 |
+
* @return void
|
26 |
+
*/
|
27 |
+
public function closeCache();
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Returns the string located at the given index from the cache.
|
31 |
+
*
|
32 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
33 |
+
* @return string The shared string at the given index
|
34 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
35 |
+
*/
|
36 |
+
public function getStringAtIndex($sharedStringIndex);
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Destroys the cache, freeing memory and removing any created artifacts
|
40 |
+
*
|
41 |
+
* @return void
|
42 |
+
*/
|
43 |
+
public function clearCache();
|
44 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/FileBasedStrategy.php
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Helper\FileSystemHelper;
|
6 |
+
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
|
7 |
+
use Box\Spout\Reader\Exception\SharedStringNotFoundException;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class FileBasedStrategy
|
11 |
+
*
|
12 |
+
* This class implements the file-based caching strategy for shared strings.
|
13 |
+
* Shared strings are stored in small files (with a max number of strings per file).
|
14 |
+
* This strategy is slower than an in-memory strategy but is used to avoid out of memory crashes.
|
15 |
+
*
|
16 |
+
* @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
|
17 |
+
*/
|
18 |
+
class FileBasedStrategy implements CachingStrategyInterface
|
19 |
+
{
|
20 |
+
/** Value to use to escape the line feed character ("\n") */
|
21 |
+
const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
|
22 |
+
|
23 |
+
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
24 |
+
protected $globalFunctionsHelper;
|
25 |
+
|
26 |
+
/** @var \Box\Spout\Common\Helper\FileSystemHelper Helper to perform file system operations */
|
27 |
+
protected $fileSystemHelper;
|
28 |
+
|
29 |
+
/** @var string Temporary folder where the temporary files will be created */
|
30 |
+
protected $tempFolder;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @var int Maximum number of strings that can be stored in one temp file
|
34 |
+
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
35 |
+
*/
|
36 |
+
protected $maxNumStringsPerTempFile;
|
37 |
+
|
38 |
+
/** @var resource Pointer to the last temp file a shared string was written to */
|
39 |
+
protected $tempFilePointer;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* @var string Path of the temporary file whose contents is currently stored in memory
|
43 |
+
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
44 |
+
*/
|
45 |
+
protected $inMemoryTempFilePath;
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @var array Contents of the temporary file that was last read
|
49 |
+
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
|
50 |
+
*/
|
51 |
+
protected $inMemoryTempFileContents;
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @param string|null $tempFolder Temporary folder where the temporary files to store shared strings will be stored
|
55 |
+
* @param int $maxNumStringsPerTempFile Maximum number of strings that can be stored in one temp file
|
56 |
+
*/
|
57 |
+
public function __construct($tempFolder, $maxNumStringsPerTempFile)
|
58 |
+
{
|
59 |
+
$rootTempFolder = ($tempFolder) ?: sys_get_temp_dir();
|
60 |
+
$this->fileSystemHelper = new FileSystemHelper($rootTempFolder);
|
61 |
+
$this->tempFolder = $this->fileSystemHelper->createFolder($rootTempFolder, uniqid('sharedstrings'));
|
62 |
+
|
63 |
+
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
|
64 |
+
|
65 |
+
$this->globalFunctionsHelper = new GlobalFunctionsHelper();
|
66 |
+
$this->tempFilePointer = null;
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Adds the given string to the cache.
|
71 |
+
*
|
72 |
+
* @param string $sharedString The string to be added to the cache
|
73 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
74 |
+
* @return void
|
75 |
+
*/
|
76 |
+
public function addStringForIndex($sharedString, $sharedStringIndex)
|
77 |
+
{
|
78 |
+
$tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex);
|
79 |
+
|
80 |
+
if (!$this->globalFunctionsHelper->file_exists($tempFilePath)) {
|
81 |
+
if ($this->tempFilePointer) {
|
82 |
+
$this->globalFunctionsHelper->fclose($this->tempFilePointer);
|
83 |
+
}
|
84 |
+
$this->tempFilePointer = $this->globalFunctionsHelper->fopen($tempFilePath, 'w');
|
85 |
+
}
|
86 |
+
|
87 |
+
// The shared string retrieval logic expects each cell data to be on one line only
|
88 |
+
// Encoding the line feed character allows to preserve this assumption
|
89 |
+
$lineFeedEncodedSharedString = $this->escapeLineFeed($sharedString);
|
90 |
+
|
91 |
+
$this->globalFunctionsHelper->fwrite($this->tempFilePointer, $lineFeedEncodedSharedString . PHP_EOL);
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Returns the path for the temp file that should contain the string for the given index
|
96 |
+
*
|
97 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
98 |
+
* @return string The temp file path for the given index
|
99 |
+
*/
|
100 |
+
protected function getSharedStringTempFilePath($sharedStringIndex)
|
101 |
+
{
|
102 |
+
$numTempFile = intval($sharedStringIndex / $this->maxNumStringsPerTempFile);
|
103 |
+
return $this->tempFolder . '/sharedstrings' . $numTempFile;
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Closes the cache after the last shared string was added.
|
108 |
+
* This prevents any additional string from being added to the cache.
|
109 |
+
*
|
110 |
+
* @return void
|
111 |
+
*/
|
112 |
+
public function closeCache()
|
113 |
+
{
|
114 |
+
// close pointer to the last temp file that was written
|
115 |
+
if ($this->tempFilePointer) {
|
116 |
+
$this->globalFunctionsHelper->fclose($this->tempFilePointer);
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Returns the string located at the given index from the cache.
|
123 |
+
*
|
124 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
125 |
+
* @return string The shared string at the given index
|
126 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
127 |
+
*/
|
128 |
+
public function getStringAtIndex($sharedStringIndex)
|
129 |
+
{
|
130 |
+
$tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex);
|
131 |
+
$indexInFile = $sharedStringIndex % $this->maxNumStringsPerTempFile;
|
132 |
+
|
133 |
+
if (!$this->globalFunctionsHelper->file_exists($tempFilePath)) {
|
134 |
+
throw new SharedStringNotFoundException("Shared string temp file not found: $tempFilePath ; for index: $sharedStringIndex");
|
135 |
+
}
|
136 |
+
|
137 |
+
if ($this->inMemoryTempFilePath !== $tempFilePath) {
|
138 |
+
// free memory
|
139 |
+
unset($this->inMemoryTempFileContents);
|
140 |
+
|
141 |
+
$this->inMemoryTempFileContents = explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath));
|
142 |
+
$this->inMemoryTempFilePath = $tempFilePath;
|
143 |
+
}
|
144 |
+
|
145 |
+
$sharedString = null;
|
146 |
+
|
147 |
+
// Using isset here because it is way faster than array_key_exists...
|
148 |
+
if (isset($this->inMemoryTempFileContents[$indexInFile])) {
|
149 |
+
$escapedSharedString = $this->inMemoryTempFileContents[$indexInFile];
|
150 |
+
$sharedString = $this->unescapeLineFeed($escapedSharedString);
|
151 |
+
}
|
152 |
+
|
153 |
+
if ($sharedString === null) {
|
154 |
+
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
|
155 |
+
}
|
156 |
+
|
157 |
+
return rtrim($sharedString, PHP_EOL);
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Escapes the line feed characters (\n)
|
162 |
+
*
|
163 |
+
* @param string $unescapedString
|
164 |
+
* @return string
|
165 |
+
*/
|
166 |
+
private function escapeLineFeed($unescapedString)
|
167 |
+
{
|
168 |
+
return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Unescapes the line feed characters (\n)
|
173 |
+
*
|
174 |
+
* @param string $escapedString
|
175 |
+
* @return string
|
176 |
+
*/
|
177 |
+
private function unescapeLineFeed($escapedString)
|
178 |
+
{
|
179 |
+
return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Destroys the cache, freeing memory and removing any created artifacts
|
184 |
+
*
|
185 |
+
* @return void
|
186 |
+
*/
|
187 |
+
public function clearCache()
|
188 |
+
{
|
189 |
+
if ($this->tempFolder) {
|
190 |
+
$this->fileSystemHelper->deleteFolderRecursively($this->tempFolder);
|
191 |
+
}
|
192 |
+
}
|
193 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/SharedStringsCaching/InMemoryStrategy.php
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\Exception\SharedStringNotFoundException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class InMemoryStrategy
|
9 |
+
*
|
10 |
+
* This class implements the in-memory caching strategy for shared strings.
|
11 |
+
* This strategy is used when the number of unique strings is low, compared to the memory available.
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
|
14 |
+
*/
|
15 |
+
class InMemoryStrategy implements CachingStrategyInterface
|
16 |
+
{
|
17 |
+
/** @var \SplFixedArray Array used to cache the shared strings */
|
18 |
+
protected $inMemoryCache;
|
19 |
+
|
20 |
+
/** @var bool Whether the cache has been closed */
|
21 |
+
protected $isCacheClosed;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* @param int $sharedStringsUniqueCount Number of unique shared strings
|
25 |
+
*/
|
26 |
+
public function __construct($sharedStringsUniqueCount)
|
27 |
+
{
|
28 |
+
$this->inMemoryCache = new \SplFixedArray($sharedStringsUniqueCount);
|
29 |
+
$this->isCacheClosed = false;
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Adds the given string to the cache.
|
34 |
+
*
|
35 |
+
* @param string $sharedString The string to be added to the cache
|
36 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
37 |
+
* @return void
|
38 |
+
*/
|
39 |
+
public function addStringForIndex($sharedString, $sharedStringIndex)
|
40 |
+
{
|
41 |
+
if (!$this->isCacheClosed) {
|
42 |
+
$this->inMemoryCache->offsetSet($sharedStringIndex, $sharedString);
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Closes the cache after the last shared string was added.
|
48 |
+
* This prevents any additional string from being added to the cache.
|
49 |
+
*
|
50 |
+
* @return void
|
51 |
+
*/
|
52 |
+
public function closeCache()
|
53 |
+
{
|
54 |
+
$this->isCacheClosed = true;
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Returns the string located at the given index from the cache.
|
59 |
+
*
|
60 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
61 |
+
* @return string The shared string at the given index
|
62 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
63 |
+
*/
|
64 |
+
public function getStringAtIndex($sharedStringIndex)
|
65 |
+
{
|
66 |
+
try {
|
67 |
+
return $this->inMemoryCache->offsetGet($sharedStringIndex);
|
68 |
+
} catch (\RuntimeException $e) {
|
69 |
+
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Destroys the cache, freeing memory and removing any created artifacts
|
75 |
+
*
|
76 |
+
* @return void
|
77 |
+
*/
|
78 |
+
public function clearCache()
|
79 |
+
{
|
80 |
+
unset($this->inMemoryCache);
|
81 |
+
$this->isCacheClosed = false;
|
82 |
+
}
|
83 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/SharedStringsHelper.php
ADDED
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\Exception\XMLProcessingException;
|
7 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
8 |
+
use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyFactory;
|
9 |
+
use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyInterface;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Class SharedStringsHelper
|
13 |
+
* This class provides helper functions for reading sharedStrings XML file
|
14 |
+
*
|
15 |
+
* @package Box\Spout\Reader\XLSX\Helper
|
16 |
+
*/
|
17 |
+
class SharedStringsHelper
|
18 |
+
{
|
19 |
+
/** Path of sharedStrings XML file inside the XLSX file */
|
20 |
+
const SHARED_STRINGS_XML_FILE_PATH = 'xl/sharedStrings.xml';
|
21 |
+
|
22 |
+
/** Main namespace for the sharedStrings.xml file */
|
23 |
+
const MAIN_NAMESPACE_FOR_SHARED_STRINGS_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
24 |
+
|
25 |
+
/** Definition of XML nodes names used to parse data */
|
26 |
+
const XML_NODE_SST = 'sst';
|
27 |
+
const XML_NODE_SI = 'si';
|
28 |
+
const XML_NODE_R = 'r';
|
29 |
+
const XML_NODE_T = 't';
|
30 |
+
|
31 |
+
/** Definition of XML attributes used to parse data */
|
32 |
+
const XML_ATTRIBUTE_COUNT = 'count';
|
33 |
+
const XML_ATTRIBUTE_UNIQUE_COUNT = 'uniqueCount';
|
34 |
+
const XML_ATTRIBUTE_XML_SPACE = 'xml:space';
|
35 |
+
const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
|
36 |
+
|
37 |
+
/** @var string Path of the XLSX file being read */
|
38 |
+
protected $filePath;
|
39 |
+
|
40 |
+
/** @var string Temporary folder where the temporary files to store shared strings will be stored */
|
41 |
+
protected $tempFolder;
|
42 |
+
|
43 |
+
/** @var CachingStrategyInterface The best caching strategy for storing shared strings */
|
44 |
+
protected $cachingStrategy;
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @param string $filePath Path of the XLSX file being read
|
48 |
+
* @param string|null|void $tempFolder Temporary folder where the temporary files to store shared strings will be stored
|
49 |
+
*/
|
50 |
+
public function __construct($filePath, $tempFolder = null)
|
51 |
+
{
|
52 |
+
$this->filePath = $filePath;
|
53 |
+
$this->tempFolder = $tempFolder;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Returns whether the XLSX file contains a shared strings XML file
|
58 |
+
*
|
59 |
+
* @return bool
|
60 |
+
*/
|
61 |
+
public function hasSharedStrings()
|
62 |
+
{
|
63 |
+
$hasSharedStrings = false;
|
64 |
+
$zip = new \ZipArchive();
|
65 |
+
|
66 |
+
if ($zip->open($this->filePath) === true) {
|
67 |
+
$hasSharedStrings = ($zip->locateName(self::SHARED_STRINGS_XML_FILE_PATH) !== false);
|
68 |
+
$zip->close();
|
69 |
+
}
|
70 |
+
|
71 |
+
return $hasSharedStrings;
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Builds an in-memory array containing all the shared strings of the sheet.
|
76 |
+
* All the strings are stored in a XML file, located at 'xl/sharedStrings.xml'.
|
77 |
+
* It is then accessed by the sheet data, via the string index in the built table.
|
78 |
+
*
|
79 |
+
* More documentation available here: http://msdn.microsoft.com/en-us/library/office/gg278314.aspx
|
80 |
+
*
|
81 |
+
* The XML file can be really big with sheets containing a lot of data. That is why
|
82 |
+
* we need to use a XML reader that provides streaming like the XMLReader library.
|
83 |
+
*
|
84 |
+
* @return void
|
85 |
+
* @throws \Box\Spout\Common\Exception\IOException If sharedStrings.xml can't be read
|
86 |
+
*/
|
87 |
+
public function extractSharedStrings()
|
88 |
+
{
|
89 |
+
$xmlReader = new XMLReader();
|
90 |
+
$sharedStringIndex = 0;
|
91 |
+
|
92 |
+
if ($xmlReader->openFileInZip($this->filePath, self::SHARED_STRINGS_XML_FILE_PATH) === false) {
|
93 |
+
throw new IOException('Could not open "' . self::SHARED_STRINGS_XML_FILE_PATH . '".');
|
94 |
+
}
|
95 |
+
|
96 |
+
try {
|
97 |
+
$sharedStringsUniqueCount = $this->getSharedStringsUniqueCount($xmlReader);
|
98 |
+
$this->cachingStrategy = $this->getBestSharedStringsCachingStrategy($sharedStringsUniqueCount);
|
99 |
+
|
100 |
+
$xmlReader->readUntilNodeFound(self::XML_NODE_SI);
|
101 |
+
|
102 |
+
while ($xmlReader->getCurrentNodeName() === self::XML_NODE_SI) {
|
103 |
+
$this->processSharedStringsItem($xmlReader, $sharedStringIndex);
|
104 |
+
$sharedStringIndex++;
|
105 |
+
|
106 |
+
// jump to the next '<si>' tag
|
107 |
+
$xmlReader->next(self::XML_NODE_SI);
|
108 |
+
}
|
109 |
+
|
110 |
+
$this->cachingStrategy->closeCache();
|
111 |
+
|
112 |
+
} catch (XMLProcessingException $exception) {
|
113 |
+
throw new IOException("The sharedStrings.xml file is invalid and cannot be read. [{$exception->getMessage()}]");
|
114 |
+
}
|
115 |
+
|
116 |
+
$xmlReader->close();
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Returns the shared strings unique count, as specified in <sst> tag.
|
121 |
+
*
|
122 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader instance
|
123 |
+
* @return int|null Number of unique shared strings in the sharedStrings.xml file
|
124 |
+
* @throws \Box\Spout\Common\Exception\IOException If sharedStrings.xml is invalid and can't be read
|
125 |
+
*/
|
126 |
+
protected function getSharedStringsUniqueCount($xmlReader)
|
127 |
+
{
|
128 |
+
$xmlReader->next(self::XML_NODE_SST);
|
129 |
+
|
130 |
+
// Iterate over the "sst" elements to get the actual "sst ELEMENT" (skips any DOCTYPE)
|
131 |
+
while ($xmlReader->getCurrentNodeName() === self::XML_NODE_SST && $xmlReader->nodeType !== XMLReader::ELEMENT) {
|
132 |
+
$xmlReader->read();
|
133 |
+
}
|
134 |
+
|
135 |
+
$uniqueCount = $xmlReader->getAttribute(self::XML_ATTRIBUTE_UNIQUE_COUNT);
|
136 |
+
|
137 |
+
// some software do not add the "uniqueCount" attribute but only use the "count" one
|
138 |
+
// @see https://github.com/box/spout/issues/254
|
139 |
+
if ($uniqueCount === null) {
|
140 |
+
$uniqueCount = $xmlReader->getAttribute(self::XML_ATTRIBUTE_COUNT);
|
141 |
+
}
|
142 |
+
|
143 |
+
return ($uniqueCount !== null) ? intval($uniqueCount) : null;
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Returns the best shared strings caching strategy.
|
148 |
+
*
|
149 |
+
* @param int|null $sharedStringsUniqueCount Number of unique shared strings (NULL if unknown)
|
150 |
+
* @return CachingStrategyInterface
|
151 |
+
*/
|
152 |
+
protected function getBestSharedStringsCachingStrategy($sharedStringsUniqueCount)
|
153 |
+
{
|
154 |
+
return CachingStrategyFactory::getInstance()
|
155 |
+
->getBestCachingStrategy($sharedStringsUniqueCount, $this->tempFolder);
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Processes the shared strings item XML node which the given XML reader is positioned on.
|
160 |
+
*
|
161 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XML Reader positioned on a "<si>" node
|
162 |
+
* @param int $sharedStringIndex Index of the processed shared strings item
|
163 |
+
* @return void
|
164 |
+
*/
|
165 |
+
protected function processSharedStringsItem($xmlReader, $sharedStringIndex)
|
166 |
+
{
|
167 |
+
$sharedStringValue = '';
|
168 |
+
|
169 |
+
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
170 |
+
$siNode = $xmlReader->expand();
|
171 |
+
$textNodes = $siNode->getElementsByTagName(self::XML_NODE_T);
|
172 |
+
|
173 |
+
foreach ($textNodes as $textNode) {
|
174 |
+
if ($this->shouldExtractTextNodeValue($textNode)) {
|
175 |
+
$textNodeValue = $textNode->nodeValue;
|
176 |
+
$shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode);
|
177 |
+
|
178 |
+
$sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : trim($textNodeValue);
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
$this->cachingStrategy->addStringForIndex($sharedStringValue, $sharedStringIndex);
|
183 |
+
}
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Not all text nodes' values must be extracted.
|
187 |
+
* Some text nodes are part of a node describing the pronunciation for instance.
|
188 |
+
* We'll only consider the nodes whose parents are "<si>" or "<r>".
|
189 |
+
*
|
190 |
+
* @param \DOMElement $textNode Text node to check
|
191 |
+
* @return bool Whether the given text node's value must be extracted
|
192 |
+
*/
|
193 |
+
protected function shouldExtractTextNodeValue($textNode)
|
194 |
+
{
|
195 |
+
$parentTagName = $textNode->parentNode->localName;
|
196 |
+
return ($parentTagName === self::XML_NODE_SI || $parentTagName === self::XML_NODE_R);
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* If the text node has the attribute 'xml:space="preserve"', then preserve whitespace.
|
201 |
+
*
|
202 |
+
* @param \DOMElement $textNode The text node element (<t>) whose whitespace may be preserved
|
203 |
+
* @return bool Whether whitespace should be preserved
|
204 |
+
*/
|
205 |
+
protected function shouldPreserveWhitespace($textNode)
|
206 |
+
{
|
207 |
+
$spaceValue = $textNode->getAttribute(self::XML_ATTRIBUTE_XML_SPACE);
|
208 |
+
return ($spaceValue === self::XML_ATTRIBUTE_VALUE_PRESERVE);
|
209 |
+
}
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Returns the shared string at the given index, using the previously chosen caching strategy.
|
213 |
+
*
|
214 |
+
* @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
|
215 |
+
* @return string The shared string at the given index
|
216 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
|
217 |
+
*/
|
218 |
+
public function getStringAtIndex($sharedStringIndex)
|
219 |
+
{
|
220 |
+
return $this->cachingStrategy->getStringAtIndex($sharedStringIndex);
|
221 |
+
}
|
222 |
+
|
223 |
+
/**
|
224 |
+
* Destroys the cache, freeing memory and removing any created artifacts
|
225 |
+
*
|
226 |
+
* @return void
|
227 |
+
*/
|
228 |
+
public function cleanup()
|
229 |
+
{
|
230 |
+
if ($this->cachingStrategy) {
|
231 |
+
$this->cachingStrategy->clearCache();
|
232 |
+
}
|
233 |
+
}
|
234 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/SheetHelper.php
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
6 |
+
use Box\Spout\Reader\XLSX\Sheet;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class SheetHelper
|
10 |
+
* This class provides helper functions related to XLSX sheets
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Reader\XLSX\Helper
|
13 |
+
*/
|
14 |
+
class SheetHelper
|
15 |
+
{
|
16 |
+
/** Paths of XML files relative to the XLSX file root */
|
17 |
+
const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
18 |
+
const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
|
19 |
+
|
20 |
+
/** Definition of XML node names used to parse data */
|
21 |
+
const XML_NODE_WORKBOOK_VIEW = 'workbookView';
|
22 |
+
const XML_NODE_SHEET = 'sheet';
|
23 |
+
const XML_NODE_SHEETS = 'sheets';
|
24 |
+
const XML_NODE_RELATIONSHIP = 'Relationship';
|
25 |
+
|
26 |
+
/** Definition of XML attributes used to parse data */
|
27 |
+
const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
|
28 |
+
const XML_ATTRIBUTE_R_ID = 'r:id';
|
29 |
+
const XML_ATTRIBUTE_NAME = 'name';
|
30 |
+
const XML_ATTRIBUTE_ID = 'Id';
|
31 |
+
const XML_ATTRIBUTE_TARGET = 'Target';
|
32 |
+
|
33 |
+
/** @var string Path of the XLSX file being read */
|
34 |
+
protected $filePath;
|
35 |
+
|
36 |
+
/** @var \Box\Spout\Reader\XLSX\ReaderOptions Reader's current options */
|
37 |
+
protected $options;
|
38 |
+
|
39 |
+
/** @var \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings */
|
40 |
+
protected $sharedStringsHelper;
|
41 |
+
|
42 |
+
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
43 |
+
protected $globalFunctionsHelper;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* @param string $filePath Path of the XLSX file being read
|
47 |
+
* @param \Box\Spout\Reader\XLSX\ReaderOptions $options Reader's current options
|
48 |
+
* @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings
|
49 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
50 |
+
*/
|
51 |
+
public function __construct($filePath, $options, $sharedStringsHelper, $globalFunctionsHelper)
|
52 |
+
{
|
53 |
+
$this->filePath = $filePath;
|
54 |
+
$this->options = $options;
|
55 |
+
$this->sharedStringsHelper = $sharedStringsHelper;
|
56 |
+
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Returns the sheets metadata of the file located at the previously given file path.
|
61 |
+
* The paths to the sheets' data are read from the [Content_Types].xml file.
|
62 |
+
*
|
63 |
+
* @return Sheet[] Sheets within the XLSX file
|
64 |
+
*/
|
65 |
+
public function getSheets()
|
66 |
+
{
|
67 |
+
$sheets = [];
|
68 |
+
$sheetIndex = 0;
|
69 |
+
$activeSheetIndex = 0; // By default, the first sheet is active
|
70 |
+
|
71 |
+
$xmlReader = new XMLReader();
|
72 |
+
if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_FILE_PATH)) {
|
73 |
+
while ($xmlReader->read()) {
|
74 |
+
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_WORKBOOK_VIEW)) {
|
75 |
+
// The "workbookView" node is located before "sheet" nodes, ensuring that
|
76 |
+
// the active sheet is known before parsing sheets data.
|
77 |
+
$activeSheetIndex = (int) $xmlReader->getAttribute(self::XML_ATTRIBUTE_ACTIVE_TAB);
|
78 |
+
} else if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_SHEET)) {
|
79 |
+
$isSheetActive = ($sheetIndex === $activeSheetIndex);
|
80 |
+
$sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex, $isSheetActive);
|
81 |
+
$sheetIndex++;
|
82 |
+
} else if ($xmlReader->isPositionedOnEndingNode(self::XML_NODE_SHEETS)) {
|
83 |
+
// stop reading once all sheets have been read
|
84 |
+
break;
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
$xmlReader->close();
|
89 |
+
}
|
90 |
+
|
91 |
+
return $sheets;
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Returns an instance of a sheet, given the XML node describing the sheet - from "workbook.xml".
|
96 |
+
* We can find the XML file path describing the sheet inside "workbook.xml.res", by mapping with the sheet ID
|
97 |
+
* ("r:id" in "workbook.xml", "Id" in "workbook.xml.res").
|
98 |
+
*
|
99 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReaderOnSheetNode XML Reader instance, pointing on the node describing the sheet, as defined in "workbook.xml"
|
100 |
+
* @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based)
|
101 |
+
* @param bool $isSheetActive Whether this sheet was defined as active
|
102 |
+
* @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
|
103 |
+
*/
|
104 |
+
protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZeroBased, $isSheetActive)
|
105 |
+
{
|
106 |
+
$sheetId = $xmlReaderOnSheetNode->getAttribute(self::XML_ATTRIBUTE_R_ID);
|
107 |
+
$escapedSheetName = $xmlReaderOnSheetNode->getAttribute(self::XML_ATTRIBUTE_NAME);
|
108 |
+
|
109 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
110 |
+
$escaper = \Box\Spout\Common\Escaper\XLSX::getInstance();
|
111 |
+
$sheetName = $escaper->unescape($escapedSheetName);
|
112 |
+
|
113 |
+
$sheetDataXMLFilePath = $this->getSheetDataXMLFilePathForSheetId($sheetId);
|
114 |
+
|
115 |
+
return new Sheet(
|
116 |
+
$this->filePath, $sheetDataXMLFilePath,
|
117 |
+
$sheetIndexZeroBased, $sheetName, $isSheetActive,
|
118 |
+
$this->options, $this->sharedStringsHelper
|
119 |
+
);
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* @param string $sheetId The sheet ID, as defined in "workbook.xml"
|
124 |
+
* @return string The XML file path describing the sheet inside "workbook.xml.res", for the given sheet ID
|
125 |
+
*/
|
126 |
+
protected function getSheetDataXMLFilePathForSheetId($sheetId)
|
127 |
+
{
|
128 |
+
$sheetDataXMLFilePath = '';
|
129 |
+
|
130 |
+
// find the file path of the sheet, by looking at the "workbook.xml.res" file
|
131 |
+
$xmlReader = new XMLReader();
|
132 |
+
if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_RELS_FILE_PATH)) {
|
133 |
+
while ($xmlReader->read()) {
|
134 |
+
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_RELATIONSHIP)) {
|
135 |
+
$relationshipSheetId = $xmlReader->getAttribute(self::XML_ATTRIBUTE_ID);
|
136 |
+
|
137 |
+
if ($relationshipSheetId === $sheetId) {
|
138 |
+
// In workbook.xml.rels, it is only "worksheets/sheet1.xml"
|
139 |
+
// In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
|
140 |
+
$sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
|
141 |
+
|
142 |
+
// sometimes, the sheet data file path already contains "/xl/"...
|
143 |
+
if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) {
|
144 |
+
$sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath;
|
145 |
+
break;
|
146 |
+
}
|
147 |
+
}
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
$xmlReader->close();
|
152 |
+
}
|
153 |
+
|
154 |
+
return $sheetDataXMLFilePath;
|
155 |
+
}
|
156 |
+
}
|
app/Services/Spout/Reader/XLSX/Helper/StyleHelper.php
ADDED
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class StyleHelper
|
9 |
+
* This class provides helper functions related to XLSX styles
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader\XLSX\Helper
|
12 |
+
*/
|
13 |
+
class StyleHelper
|
14 |
+
{
|
15 |
+
/** Paths of XML files relative to the XLSX file root */
|
16 |
+
const STYLES_XML_FILE_PATH = 'xl/styles.xml';
|
17 |
+
|
18 |
+
/** Nodes used to find relevant information in the styles XML file */
|
19 |
+
const XML_NODE_NUM_FMTS = 'numFmts';
|
20 |
+
const XML_NODE_NUM_FMT = 'numFmt';
|
21 |
+
const XML_NODE_CELL_XFS = 'cellXfs';
|
22 |
+
const XML_NODE_XF = 'xf';
|
23 |
+
|
24 |
+
/** Attributes used to find relevant information in the styles XML file */
|
25 |
+
const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
|
26 |
+
const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
|
27 |
+
const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
|
28 |
+
|
29 |
+
/** By convention, default style ID is 0 */
|
30 |
+
const DEFAULT_STYLE_ID = 0;
|
31 |
+
|
32 |
+
const NUMBER_FORMAT_GENERAL = 'General';
|
33 |
+
|
34 |
+
/**
|
35 |
+
* @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx
|
36 |
+
* @var array Mapping between built-in numFmtId and the associated format - for dates only
|
37 |
+
*/
|
38 |
+
protected static $builtinNumFmtIdToNumFormatMapping = [
|
39 |
+
14 => 'm/d/yyyy', // @NOTE: ECMA spec is 'mm-dd-yy'
|
40 |
+
15 => 'd-mmm-yy',
|
41 |
+
16 => 'd-mmm',
|
42 |
+
17 => 'mmm-yy',
|
43 |
+
18 => 'h:mm AM/PM',
|
44 |
+
19 => 'h:mm:ss AM/PM',
|
45 |
+
20 => 'h:mm',
|
46 |
+
21 => 'h:mm:ss',
|
47 |
+
22 => 'm/d/yyyy h:mm', // @NOTE: ECMA spec is 'm/d/yy h:mm',
|
48 |
+
45 => 'mm:ss',
|
49 |
+
46 => '[h]:mm:ss',
|
50 |
+
47 => 'mm:ss.0', // @NOTE: ECMA spec is 'mmss.0',
|
51 |
+
];
|
52 |
+
|
53 |
+
/** @var string Path of the XLSX file being read */
|
54 |
+
protected $filePath;
|
55 |
+
|
56 |
+
/** @var array Array containing the IDs of built-in number formats indicating a date */
|
57 |
+
protected $builtinNumFmtIdIndicatingDates;
|
58 |
+
|
59 |
+
/** @var array Array containing a mapping NUM_FMT_ID => FORMAT_CODE */
|
60 |
+
protected $customNumberFormats;
|
61 |
+
|
62 |
+
/** @var array Array containing a mapping STYLE_ID => [STYLE_ATTRIBUTES] */
|
63 |
+
protected $stylesAttributes;
|
64 |
+
|
65 |
+
/** @var array Cache containing a mapping NUM_FMT_ID => IS_DATE_FORMAT. Used to avoid lots of recalculations */
|
66 |
+
protected $numFmtIdToIsDateFormatCache = [];
|
67 |
+
|
68 |
+
/**
|
69 |
+
* @param string $filePath Path of the XLSX file being read
|
70 |
+
*/
|
71 |
+
public function __construct($filePath)
|
72 |
+
{
|
73 |
+
$this->filePath = $filePath;
|
74 |
+
$this->builtinNumFmtIdIndicatingDates = array_keys(self::$builtinNumFmtIdToNumFormatMapping);
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Returns whether the style with the given ID should consider
|
79 |
+
* numeric values as timestamps and format the cell as a date.
|
80 |
+
*
|
81 |
+
* @param int $styleId Zero-based style ID
|
82 |
+
* @return bool Whether the cell with the given cell should display a date instead of a numeric value
|
83 |
+
*/
|
84 |
+
public function shouldFormatNumericValueAsDate($styleId)
|
85 |
+
{
|
86 |
+
$stylesAttributes = $this->getStylesAttributes();
|
87 |
+
|
88 |
+
// Default style (0) does not format numeric values as timestamps. Only custom styles do.
|
89 |
+
// Also if the style ID does not exist in the styles.xml file, format as numeric value.
|
90 |
+
// Using isset here because it is way faster than array_key_exists...
|
91 |
+
if ($styleId === self::DEFAULT_STYLE_ID || !isset($stylesAttributes[$styleId])) {
|
92 |
+
return false;
|
93 |
+
}
|
94 |
+
|
95 |
+
$styleAttributes = $stylesAttributes[$styleId];
|
96 |
+
|
97 |
+
return $this->doesStyleIndicateDate($styleAttributes);
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Reads the styles.xml file and extract the relevant information from the file.
|
102 |
+
*
|
103 |
+
* @return void
|
104 |
+
*/
|
105 |
+
protected function extractRelevantInfo()
|
106 |
+
{
|
107 |
+
$this->customNumberFormats = [];
|
108 |
+
$this->stylesAttributes = [];
|
109 |
+
|
110 |
+
$xmlReader = new XMLReader();
|
111 |
+
|
112 |
+
if ($xmlReader->openFileInZip($this->filePath, self::STYLES_XML_FILE_PATH)) {
|
113 |
+
while ($xmlReader->read()) {
|
114 |
+
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_NUM_FMTS)) {
|
115 |
+
$this->extractNumberFormats($xmlReader);
|
116 |
+
|
117 |
+
} else if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_CELL_XFS)) {
|
118 |
+
$this->extractStyleAttributes($xmlReader);
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
$xmlReader->close();
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Extracts number formats from the "numFmt" nodes.
|
128 |
+
* For simplicity, the styles attributes are kept in memory. This is possible thanks
|
129 |
+
* to the reuse of formats. So 1 million cells should not use 1 million formats.
|
130 |
+
*
|
131 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XML Reader positioned on the "numFmts" node
|
132 |
+
* @return void
|
133 |
+
*/
|
134 |
+
protected function extractNumberFormats($xmlReader)
|
135 |
+
{
|
136 |
+
while ($xmlReader->read()) {
|
137 |
+
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_NUM_FMT)) {
|
138 |
+
$numFmtId = intval($xmlReader->getAttribute(self::XML_ATTRIBUTE_NUM_FMT_ID));
|
139 |
+
$formatCode = $xmlReader->getAttribute(self::XML_ATTRIBUTE_FORMAT_CODE);
|
140 |
+
$this->customNumberFormats[$numFmtId] = $formatCode;
|
141 |
+
} else if ($xmlReader->isPositionedOnEndingNode(self::XML_NODE_NUM_FMTS)) {
|
142 |
+
// Once done reading "numFmts" node's children
|
143 |
+
break;
|
144 |
+
}
|
145 |
+
}
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Extracts style attributes from the "xf" nodes, inside the "cellXfs" section.
|
150 |
+
* For simplicity, the styles attributes are kept in memory. This is possible thanks
|
151 |
+
* to the reuse of styles. So 1 million cells should not use 1 million styles.
|
152 |
+
*
|
153 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XML Reader positioned on the "cellXfs" node
|
154 |
+
* @return void
|
155 |
+
*/
|
156 |
+
protected function extractStyleAttributes($xmlReader)
|
157 |
+
{
|
158 |
+
while ($xmlReader->read()) {
|
159 |
+
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_XF)) {
|
160 |
+
$numFmtId = $xmlReader->getAttribute(self::XML_ATTRIBUTE_NUM_FMT_ID);
|
161 |
+
$normalizedNumFmtId = ($numFmtId !== null) ? intval($numFmtId) : null;
|
162 |
+
|
163 |
+
$applyNumberFormat = $xmlReader->getAttribute(self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT);
|
164 |
+
$normalizedApplyNumberFormat = ($applyNumberFormat !== null) ? !!$applyNumberFormat : null;
|
165 |
+
|
166 |
+
$this->stylesAttributes[] = [
|
167 |
+
self::XML_ATTRIBUTE_NUM_FMT_ID => $normalizedNumFmtId,
|
168 |
+
self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT => $normalizedApplyNumberFormat,
|
169 |
+
];
|
170 |
+
} else if ($xmlReader->isPositionedOnEndingNode(self::XML_NODE_CELL_XFS)) {
|
171 |
+
// Once done reading "cellXfs" node's children
|
172 |
+
break;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* @return array The custom number formats
|
179 |
+
*/
|
180 |
+
protected function getCustomNumberFormats()
|
181 |
+
{
|
182 |
+
if (!isset($this->customNumberFormats)) {
|
183 |
+
$this->extractRelevantInfo();
|
184 |
+
}
|
185 |
+
|
186 |
+
return $this->customNumberFormats;
|
187 |
+
}
|
188 |
+
|
189 |
+
/**
|
190 |
+
* @return array The styles attributes
|
191 |
+
*/
|
192 |
+
protected function getStylesAttributes()
|
193 |
+
{
|
194 |
+
if (!isset($this->stylesAttributes)) {
|
195 |
+
$this->extractRelevantInfo();
|
196 |
+
}
|
197 |
+
|
198 |
+
return $this->stylesAttributes;
|
199 |
+
}
|
200 |
+
|
201 |
+
/**
|
202 |
+
* @param array $styleAttributes Array containing the style attributes (2 keys: "applyNumberFormat" and "numFmtId")
|
203 |
+
* @return bool Whether the style with the given attributes indicates that the number is a date
|
204 |
+
*/
|
205 |
+
protected function doesStyleIndicateDate($styleAttributes)
|
206 |
+
{
|
207 |
+
$applyNumberFormat = $styleAttributes[self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT];
|
208 |
+
$numFmtId = $styleAttributes[self::XML_ATTRIBUTE_NUM_FMT_ID];
|
209 |
+
|
210 |
+
// A style may apply a date format if it has:
|
211 |
+
// - "applyNumberFormat" attribute not set to "false"
|
212 |
+
// - "numFmtId" attribute set
|
213 |
+
// This is a preliminary check, as having "numFmtId" set just means the style should apply a specific number format,
|
214 |
+
// but this is not necessarily a date.
|
215 |
+
if ($applyNumberFormat === false || $numFmtId === null) {
|
216 |
+
return false;
|
217 |
+
}
|
218 |
+
|
219 |
+
return $this->doesNumFmtIdIndicateDate($numFmtId);
|
220 |
+
}
|
221 |
+
|
222 |
+
/**
|
223 |
+
* Returns whether the number format ID indicates that the number is a date.
|
224 |
+
* The result is cached to avoid recomputing the same thing over and over, as
|
225 |
+
* "numFmtId" attributes can be shared between multiple styles.
|
226 |
+
*
|
227 |
+
* @param int $numFmtId
|
228 |
+
* @return bool Whether the number format ID indicates that the number is a date
|
229 |
+
*/
|
230 |
+
protected function doesNumFmtIdIndicateDate($numFmtId)
|
231 |
+
{
|
232 |
+
if (!isset($this->numFmtIdToIsDateFormatCache[$numFmtId])) {
|
233 |
+
$formatCode = $this->getFormatCodeForNumFmtId($numFmtId);
|
234 |
+
|
235 |
+
$this->numFmtIdToIsDateFormatCache[$numFmtId] = (
|
236 |
+
$this->isNumFmtIdBuiltInDateFormat($numFmtId) ||
|
237 |
+
$this->isFormatCodeCustomDateFormat($formatCode)
|
238 |
+
);
|
239 |
+
}
|
240 |
+
|
241 |
+
return $this->numFmtIdToIsDateFormatCache[$numFmtId];
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* @param int $numFmtId
|
246 |
+
* @return string|null The custom number format or NULL if none defined for the given numFmtId
|
247 |
+
*/
|
248 |
+
protected function getFormatCodeForNumFmtId($numFmtId)
|
249 |
+
{
|
250 |
+
$customNumberFormats = $this->getCustomNumberFormats();
|
251 |
+
|
252 |
+
// Using isset here because it is way faster than array_key_exists...
|
253 |
+
return (isset($customNumberFormats[$numFmtId])) ? $customNumberFormats[$numFmtId] : null;
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* @param int $numFmtId
|
258 |
+
* @return bool Whether the number format ID indicates that the number is a date
|
259 |
+
*/
|
260 |
+
protected function isNumFmtIdBuiltInDateFormat($numFmtId)
|
261 |
+
{
|
262 |
+
return in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates);
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* @param string|null $formatCode
|
267 |
+
* @return bool Whether the given format code indicates that the number is a date
|
268 |
+
*/
|
269 |
+
protected function isFormatCodeCustomDateFormat($formatCode)
|
270 |
+
{
|
271 |
+
// if no associated format code or if using the default "General" format
|
272 |
+
if ($formatCode === null || strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) {
|
273 |
+
return false;
|
274 |
+
}
|
275 |
+
|
276 |
+
return $this->isFormatCodeMatchingDateFormatPattern($formatCode);
|
277 |
+
}
|
278 |
+
|
279 |
+
/**
|
280 |
+
* @param string $formatCode
|
281 |
+
* @return bool Whether the given format code matches a date format pattern
|
282 |
+
*/
|
283 |
+
protected function isFormatCodeMatchingDateFormatPattern($formatCode)
|
284 |
+
{
|
285 |
+
// Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\")
|
286 |
+
$pattern = '((?<!\\\)\[.+?(?<!\\\)\])';
|
287 |
+
$formatCode = preg_replace($pattern, '', $formatCode);
|
288 |
+
|
289 |
+
// custom date formats contain specific characters to represent the date:
|
290 |
+
// e - yy - m - d - h - s
|
291 |
+
// and all of their variants (yyyy - mm - dd...)
|
292 |
+
$dateFormatCharacters = ['e', 'yy', 'm', 'd', 'h', 's'];
|
293 |
+
|
294 |
+
$hasFoundDateFormatCharacter = false;
|
295 |
+
foreach ($dateFormatCharacters as $dateFormatCharacter) {
|
296 |
+
// character not preceded by "\" (case insensitive)
|
297 |
+
$pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i';
|
298 |
+
|
299 |
+
if (preg_match($pattern, $formatCode)) {
|
300 |
+
$hasFoundDateFormatCharacter = true;
|
301 |
+
break;
|
302 |
+
}
|
303 |
+
}
|
304 |
+
|
305 |
+
return $hasFoundDateFormatCharacter;
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* Returns the format as defined in "styles.xml" of the given style.
|
310 |
+
* NOTE: It is assumed that the style DOES have a number format associated to it.
|
311 |
+
*
|
312 |
+
* @param int $styleId Zero-based style ID
|
313 |
+
* @return string The number format code associated with the given style
|
314 |
+
*/
|
315 |
+
public function getNumberFormatCode($styleId)
|
316 |
+
{
|
317 |
+
$stylesAttributes = $this->getStylesAttributes();
|
318 |
+
$styleAttributes = $stylesAttributes[$styleId];
|
319 |
+
$numFmtId = $styleAttributes[self::XML_ATTRIBUTE_NUM_FMT_ID];
|
320 |
+
|
321 |
+
if ($this->isNumFmtIdBuiltInDateFormat($numFmtId)) {
|
322 |
+
$numberFormatCode = self::$builtinNumFmtIdToNumFormatMapping[$numFmtId];
|
323 |
+
} else {
|
324 |
+
$customNumberFormats = $this->getCustomNumberFormats();
|
325 |
+
$numberFormatCode = $customNumberFormats[$numFmtId];
|
326 |
+
}
|
327 |
+
|
328 |
+
return $numberFormatCode;
|
329 |
+
}
|
330 |
+
}
|
app/Services/Spout/Reader/XLSX/Reader.php
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\AbstractReader;
|
7 |
+
use Box\Spout\Reader\XLSX\Helper\SharedStringsHelper;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class Reader
|
11 |
+
* This class provides support to read data from a XLSX file
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Reader\XLSX
|
14 |
+
*/
|
15 |
+
class Reader extends AbstractReader
|
16 |
+
{
|
17 |
+
/** @var \ZipArchive */
|
18 |
+
protected $zip;
|
19 |
+
|
20 |
+
/** @var \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings */
|
21 |
+
protected $sharedStringsHelper;
|
22 |
+
|
23 |
+
/** @var SheetIterator To iterator over the XLSX sheets */
|
24 |
+
protected $sheetIterator;
|
25 |
+
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Returns the reader's current options
|
29 |
+
*
|
30 |
+
* @return ReaderOptions
|
31 |
+
*/
|
32 |
+
protected function getOptions()
|
33 |
+
{
|
34 |
+
if (!isset($this->options)) {
|
35 |
+
$this->options = new ReaderOptions();
|
36 |
+
}
|
37 |
+
return $this->options;
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* @param string $tempFolder Temporary folder where the temporary files will be created
|
42 |
+
* @return Reader
|
43 |
+
*/
|
44 |
+
public function setTempFolder($tempFolder)
|
45 |
+
{
|
46 |
+
$this->getOptions()->setTempFolder($tempFolder);
|
47 |
+
return $this;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Returns whether stream wrappers are supported
|
52 |
+
*
|
53 |
+
* @return bool
|
54 |
+
*/
|
55 |
+
protected function doesSupportStreamWrapper()
|
56 |
+
{
|
57 |
+
return false;
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Opens the file at the given file path to make it ready to be read.
|
62 |
+
* It also parses the sharedStrings.xml file to get all the shared strings available in memory
|
63 |
+
* and fetches all the available sheets.
|
64 |
+
*
|
65 |
+
* @param string $filePath Path of the file to be read
|
66 |
+
* @return void
|
67 |
+
* @throws \Box\Spout\Common\Exception\IOException If the file at the given path or its content cannot be read
|
68 |
+
* @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
|
69 |
+
*/
|
70 |
+
protected function openReader($filePath)
|
71 |
+
{
|
72 |
+
$this->zip = new \ZipArchive();
|
73 |
+
|
74 |
+
if ($this->zip->open($filePath) === true) {
|
75 |
+
$this->sharedStringsHelper = new SharedStringsHelper($filePath, $this->getOptions()->getTempFolder());
|
76 |
+
|
77 |
+
if ($this->sharedStringsHelper->hasSharedStrings()) {
|
78 |
+
// Extracts all the strings from the sheets for easy access in the future
|
79 |
+
$this->sharedStringsHelper->extractSharedStrings();
|
80 |
+
}
|
81 |
+
|
82 |
+
$this->sheetIterator = new SheetIterator($filePath, $this->getOptions(), $this->sharedStringsHelper, $this->globalFunctionsHelper);
|
83 |
+
} else {
|
84 |
+
throw new IOException("Could not open $filePath for reading.");
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Returns an iterator to iterate over sheets.
|
90 |
+
*
|
91 |
+
* @return SheetIterator To iterate over sheets
|
92 |
+
*/
|
93 |
+
protected function getConcreteSheetIterator()
|
94 |
+
{
|
95 |
+
return $this->sheetIterator;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Closes the reader. To be used after reading the file.
|
100 |
+
*
|
101 |
+
* @return void
|
102 |
+
*/
|
103 |
+
protected function closeReader()
|
104 |
+
{
|
105 |
+
if ($this->zip) {
|
106 |
+
$this->zip->close();
|
107 |
+
}
|
108 |
+
|
109 |
+
if ($this->sharedStringsHelper) {
|
110 |
+
$this->sharedStringsHelper->cleanup();
|
111 |
+
}
|
112 |
+
}
|
113 |
+
}
|
app/Services/Spout/Reader/XLSX/ReaderOptions.php
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class ReaderOptions
|
7 |
+
* This class is used to customize the reader's behavior
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Reader\XLSX
|
10 |
+
*/
|
11 |
+
class ReaderOptions extends \Box\Spout\Reader\Common\ReaderOptions
|
12 |
+
{
|
13 |
+
/** @var string|null Temporary folder where the temporary files will be created */
|
14 |
+
protected $tempFolder = null;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @return string|null Temporary folder where the temporary files will be created
|
18 |
+
*/
|
19 |
+
public function getTempFolder()
|
20 |
+
{
|
21 |
+
return $this->tempFolder;
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @param string|null $tempFolder Temporary folder where the temporary files will be created
|
26 |
+
* @return ReaderOptions
|
27 |
+
*/
|
28 |
+
public function setTempFolder($tempFolder)
|
29 |
+
{
|
30 |
+
$this->tempFolder = $tempFolder;
|
31 |
+
return $this;
|
32 |
+
}
|
33 |
+
}
|
app/Services/Spout/Reader/XLSX/RowIterator.php
ADDED
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Reader\Exception\XMLProcessingException;
|
7 |
+
use Box\Spout\Reader\IteratorInterface;
|
8 |
+
use Box\Spout\Reader\Wrapper\XMLReader;
|
9 |
+
use Box\Spout\Reader\XLSX\Helper\CellHelper;
|
10 |
+
use Box\Spout\Reader\XLSX\Helper\CellValueFormatter;
|
11 |
+
use Box\Spout\Reader\XLSX\Helper\StyleHelper;
|
12 |
+
use Box\Spout\Reader\Common\XMLProcessor;
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Class RowIterator
|
16 |
+
*
|
17 |
+
* @package Box\Spout\Reader\XLSX
|
18 |
+
*/
|
19 |
+
class RowIterator implements IteratorInterface
|
20 |
+
{
|
21 |
+
/** Definition of XML nodes names used to parse data */
|
22 |
+
const XML_NODE_DIMENSION = 'dimension';
|
23 |
+
const XML_NODE_WORKSHEET = 'worksheet';
|
24 |
+
const XML_NODE_ROW = 'row';
|
25 |
+
const XML_NODE_CELL = 'c';
|
26 |
+
|
27 |
+
/** Definition of XML attributes used to parse data */
|
28 |
+
const XML_ATTRIBUTE_REF = 'ref';
|
29 |
+
const XML_ATTRIBUTE_SPANS = 'spans';
|
30 |
+
const XML_ATTRIBUTE_ROW_INDEX = 'r';
|
31 |
+
const XML_ATTRIBUTE_CELL_INDEX = 'r';
|
32 |
+
|
33 |
+
/** @var string Path of the XLSX file being read */
|
34 |
+
protected $filePath;
|
35 |
+
|
36 |
+
/** @var string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml */
|
37 |
+
protected $sheetDataXMLFilePath;
|
38 |
+
|
39 |
+
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
40 |
+
protected $xmlReader;
|
41 |
+
|
42 |
+
/** @var \Box\Spout\Reader\Common\XMLProcessor Helper Object to process XML nodes */
|
43 |
+
protected $xmlProcessor;
|
44 |
+
|
45 |
+
/** @var Helper\CellValueFormatter Helper to format cell values */
|
46 |
+
protected $cellValueFormatter;
|
47 |
+
|
48 |
+
/** @var Helper\StyleHelper $styleHelper Helper to work with styles */
|
49 |
+
protected $styleHelper;
|
50 |
+
|
51 |
+
/**
|
52 |
+
* TODO: This variable can be deleted when row indices get preserved
|
53 |
+
* @var int Number of read rows
|
54 |
+
*/
|
55 |
+
protected $numReadRows = 0;
|
56 |
+
|
57 |
+
/** @var array Contains the data for the currently processed row (key = cell index, value = cell value) */
|
58 |
+
protected $currentlyProcessedRowData = [];
|
59 |
+
|
60 |
+
/** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
|
61 |
+
protected $rowDataBuffer = null;
|
62 |
+
|
63 |
+
/** @var bool Indicates whether all rows have been read */
|
64 |
+
protected $hasReachedEndOfFile = false;
|
65 |
+
|
66 |
+
/** @var int The number of columns the sheet has (0 meaning undefined) */
|
67 |
+
protected $numColumns = 0;
|
68 |
+
|
69 |
+
/** @var bool Whether empty rows should be returned or skipped */
|
70 |
+
protected $shouldPreserveEmptyRows;
|
71 |
+
|
72 |
+
/** @var int Last row index processed (one-based) */
|
73 |
+
protected $lastRowIndexProcessed = 0;
|
74 |
+
|
75 |
+
/** @var int Row index to be processed next (one-based) */
|
76 |
+
protected $nextRowIndexToBeProcessed = 0;
|
77 |
+
|
78 |
+
/** @var int Last column index processed (zero-based) */
|
79 |
+
protected $lastColumnIndexProcessed = -1;
|
80 |
+
|
81 |
+
/**
|
82 |
+
* @param string $filePath Path of the XLSX file being read
|
83 |
+
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
|
84 |
+
* @param \Box\Spout\Reader\XLSX\ReaderOptions $options Reader's current options
|
85 |
+
* @param Helper\SharedStringsHelper $sharedStringsHelper Helper to work with shared strings
|
86 |
+
*/
|
87 |
+
public function __construct($filePath, $sheetDataXMLFilePath, $options, $sharedStringsHelper)
|
88 |
+
{
|
89 |
+
$this->filePath = $filePath;
|
90 |
+
$this->sheetDataXMLFilePath = $this->normalizeSheetDataXMLFilePath($sheetDataXMLFilePath);
|
91 |
+
|
92 |
+
$this->xmlReader = new XMLReader();
|
93 |
+
|
94 |
+
$this->styleHelper = new StyleHelper($filePath);
|
95 |
+
$this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper, $this->styleHelper, $options->shouldFormatDates());
|
96 |
+
|
97 |
+
$this->shouldPreserveEmptyRows = $options->shouldPreserveEmptyRows();
|
98 |
+
|
99 |
+
// Register all callbacks to process different nodes when reading the XML file
|
100 |
+
$this->xmlProcessor = new XMLProcessor($this->xmlReader);
|
101 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_DIMENSION, XMLProcessor::NODE_TYPE_START, [$this, 'processDimensionStartingNode']);
|
102 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_ROW, XMLProcessor::NODE_TYPE_START, [$this, 'processRowStartingNode']);
|
103 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_CELL, XMLProcessor::NODE_TYPE_START, [$this, 'processCellStartingNode']);
|
104 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_ROW, XMLProcessor::NODE_TYPE_END, [$this, 'processRowEndingNode']);
|
105 |
+
$this->xmlProcessor->registerCallback(self::XML_NODE_WORKSHEET, XMLProcessor::NODE_TYPE_END, [$this, 'processWorksheetEndingNode']);
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
|
110 |
+
* @return string Path of the XML file containing the sheet data,
|
111 |
+
* without the leading slash.
|
112 |
+
*/
|
113 |
+
protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
|
114 |
+
{
|
115 |
+
return ltrim($sheetDataXMLFilePath, '/');
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Rewind the Iterator to the first element.
|
120 |
+
* Initializes the XMLReader object that reads the associated sheet data.
|
121 |
+
* The XMLReader is configured to be safe from billion laughs attack.
|
122 |
+
* @link http://php.net/manual/en/iterator.rewind.php
|
123 |
+
*
|
124 |
+
* @return void
|
125 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read
|
126 |
+
*/
|
127 |
+
public function rewind()
|
128 |
+
{
|
129 |
+
$this->xmlReader->close();
|
130 |
+
|
131 |
+
if ($this->xmlReader->openFileInZip($this->filePath, $this->sheetDataXMLFilePath) === false) {
|
132 |
+
throw new IOException("Could not open \"{$this->sheetDataXMLFilePath}\".");
|
133 |
+
}
|
134 |
+
|
135 |
+
$this->numReadRows = 0;
|
136 |
+
$this->lastRowIndexProcessed = 0;
|
137 |
+
$this->nextRowIndexToBeProcessed = 0;
|
138 |
+
$this->rowDataBuffer = null;
|
139 |
+
$this->hasReachedEndOfFile = false;
|
140 |
+
$this->numColumns = 0;
|
141 |
+
|
142 |
+
$this->next();
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Checks if current position is valid
|
147 |
+
* @link http://php.net/manual/en/iterator.valid.php
|
148 |
+
*
|
149 |
+
* @return bool
|
150 |
+
*/
|
151 |
+
public function valid()
|
152 |
+
{
|
153 |
+
return (!$this->hasReachedEndOfFile);
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Move forward to next element. Reads data describing the next unprocessed row.
|
158 |
+
* @link http://php.net/manual/en/iterator.next.php
|
159 |
+
*
|
160 |
+
* @return void
|
161 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
162 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
|
163 |
+
*/
|
164 |
+
public function next()
|
165 |
+
{
|
166 |
+
$this->nextRowIndexToBeProcessed++;
|
167 |
+
|
168 |
+
if ($this->doesNeedDataForNextRowToBeProcessed()) {
|
169 |
+
$this->readDataForNextRow();
|
170 |
+
}
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Returns whether we need data for the next row to be processed.
|
175 |
+
* We don't need to read data if:
|
176 |
+
* we have already read at least one row
|
177 |
+
* AND
|
178 |
+
* we need to preserve empty rows
|
179 |
+
* AND
|
180 |
+
* the last row that was read is not the row that need to be processed
|
181 |
+
* (i.e. if we need to return empty rows)
|
182 |
+
*
|
183 |
+
* @return bool Whether we need data for the next row to be processed.
|
184 |
+
*/
|
185 |
+
protected function doesNeedDataForNextRowToBeProcessed()
|
186 |
+
{
|
187 |
+
$hasReadAtLeastOneRow = ($this->lastRowIndexProcessed !== 0);
|
188 |
+
|
189 |
+
return (
|
190 |
+
!$hasReadAtLeastOneRow ||
|
191 |
+
!$this->shouldPreserveEmptyRows ||
|
192 |
+
$this->lastRowIndexProcessed < $this->nextRowIndexToBeProcessed
|
193 |
+
);
|
194 |
+
}
|
195 |
+
|
196 |
+
/**
|
197 |
+
* @return void
|
198 |
+
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
|
199 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
|
200 |
+
*/
|
201 |
+
protected function readDataForNextRow()
|
202 |
+
{
|
203 |
+
$this->currentlyProcessedRowData = [];
|
204 |
+
|
205 |
+
try {
|
206 |
+
$this->xmlProcessor->readUntilStopped();
|
207 |
+
} catch (XMLProcessingException $exception) {
|
208 |
+
throw new IOException("The {$this->sheetDataXMLFilePath} file cannot be read. [{$exception->getMessage()}]");
|
209 |
+
}
|
210 |
+
|
211 |
+
$this->rowDataBuffer = $this->currentlyProcessedRowData;
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<dimension>" starting node
|
216 |
+
* @return int A return code that indicates what action should the processor take next
|
217 |
+
*/
|
218 |
+
protected function processDimensionStartingNode($xmlReader)
|
219 |
+
{
|
220 |
+
// Read dimensions of the sheet
|
221 |
+
$dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
|
222 |
+
if (preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) {
|
223 |
+
$this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1;
|
224 |
+
}
|
225 |
+
|
226 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<row>" starting node
|
231 |
+
* @return int A return code that indicates what action should the processor take next
|
232 |
+
*/
|
233 |
+
protected function processRowStartingNode($xmlReader)
|
234 |
+
{
|
235 |
+
// Reset index of the last processed column
|
236 |
+
$this->lastColumnIndexProcessed = -1;
|
237 |
+
|
238 |
+
// Mark the last processed row as the one currently being read
|
239 |
+
$this->lastRowIndexProcessed = $this->getRowIndex($xmlReader);
|
240 |
+
|
241 |
+
// Read spans info if present
|
242 |
+
$numberOfColumnsForRow = $this->numColumns;
|
243 |
+
$spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
|
244 |
+
if ($spans) {
|
245 |
+
list(, $numberOfColumnsForRow) = explode(':', $spans);
|
246 |
+
$numberOfColumnsForRow = intval($numberOfColumnsForRow);
|
247 |
+
}
|
248 |
+
|
249 |
+
$this->currentlyProcessedRowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
|
250 |
+
|
251 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
252 |
+
}
|
253 |
+
|
254 |
+
/**
|
255 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<cell>" starting node
|
256 |
+
* @return int A return code that indicates what action should the processor take next
|
257 |
+
*/
|
258 |
+
protected function processCellStartingNode($xmlReader)
|
259 |
+
{
|
260 |
+
$currentColumnIndex = $this->getColumnIndex($xmlReader);
|
261 |
+
|
262 |
+
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
263 |
+
$node = $xmlReader->expand();
|
264 |
+
$this->currentlyProcessedRowData[$currentColumnIndex] = $this->getCellValue($node);
|
265 |
+
$this->lastColumnIndexProcessed = $currentColumnIndex;
|
266 |
+
|
267 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
268 |
+
}
|
269 |
+
|
270 |
+
/**
|
271 |
+
* @return int A return code that indicates what action should the processor take next
|
272 |
+
*/
|
273 |
+
protected function processRowEndingNode()
|
274 |
+
{
|
275 |
+
// if the fetched row is empty and we don't want to preserve it..,
|
276 |
+
if (!$this->shouldPreserveEmptyRows && $this->isEmptyRow($this->currentlyProcessedRowData)) {
|
277 |
+
// ... skip it
|
278 |
+
return XMLProcessor::PROCESSING_CONTINUE;
|
279 |
+
}
|
280 |
+
|
281 |
+
$this->numReadRows++;
|
282 |
+
|
283 |
+
// If needed, we fill the empty cells
|
284 |
+
if ($this->numColumns === 0) {
|
285 |
+
$this->currentlyProcessedRowData = CellHelper::fillMissingArrayIndexes($this->currentlyProcessedRowData);
|
286 |
+
}
|
287 |
+
|
288 |
+
// at this point, we have all the data we need for the row
|
289 |
+
// so that we can populate the buffer
|
290 |
+
return XMLProcessor::PROCESSING_STOP;
|
291 |
+
}
|
292 |
+
|
293 |
+
/**
|
294 |
+
* @return int A return code that indicates what action should the processor take next
|
295 |
+
*/
|
296 |
+
protected function processWorksheetEndingNode()
|
297 |
+
{
|
298 |
+
// The closing "</worksheet>" marks the end of the file
|
299 |
+
$this->hasReachedEndOfFile = true;
|
300 |
+
|
301 |
+
return XMLProcessor::PROCESSING_STOP;
|
302 |
+
}
|
303 |
+
|
304 |
+
/**
|
305 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<row>" node
|
306 |
+
* @return int Row index
|
307 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException When the given cell index is invalid
|
308 |
+
*/
|
309 |
+
protected function getRowIndex($xmlReader)
|
310 |
+
{
|
311 |
+
// Get "r" attribute if present (from something like <row r="3"...>
|
312 |
+
$currentRowIndex = $xmlReader->getAttribute(self::XML_ATTRIBUTE_ROW_INDEX);
|
313 |
+
|
314 |
+
return ($currentRowIndex !== null) ?
|
315 |
+
intval($currentRowIndex) :
|
316 |
+
$this->lastRowIndexProcessed + 1;
|
317 |
+
}
|
318 |
+
|
319 |
+
/**
|
320 |
+
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<c>" node
|
321 |
+
* @return int Column index
|
322 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException When the given cell index is invalid
|
323 |
+
*/
|
324 |
+
protected function getColumnIndex($xmlReader)
|
325 |
+
{
|
326 |
+
// Get "r" attribute if present (from something like <c r="A1"...>
|
327 |
+
$currentCellIndex = $xmlReader->getAttribute(self::XML_ATTRIBUTE_CELL_INDEX);
|
328 |
+
|
329 |
+
return ($currentCellIndex !== null) ?
|
330 |
+
CellHelper::getColumnIndexFromCellIndex($currentCellIndex) :
|
331 |
+
$this->lastColumnIndexProcessed + 1;
|
332 |
+
}
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
336 |
+
*
|
337 |
+
* @param \DOMNode $node
|
338 |
+
* @return string|int|float|bool|\DateTime|null The value associated with the cell (null when the cell has an error)
|
339 |
+
*/
|
340 |
+
protected function getCellValue($node)
|
341 |
+
{
|
342 |
+
return $this->cellValueFormatter->extractAndFormatNodeValue($node);
|
343 |
+
}
|
344 |
+
|
345 |
+
/**
|
346 |
+
* @param array $rowData
|
347 |
+
* @return bool Whether the given row is empty
|
348 |
+
*/
|
349 |
+
protected function isEmptyRow($rowData)
|
350 |
+
{
|
351 |
+
return (count($rowData) === 1 && key($rowData) === '');
|
352 |
+
}
|
353 |
+
|
354 |
+
/**
|
355 |
+
* Return the current element, either an empty row or from the buffer.
|
356 |
+
* @link http://php.net/manual/en/iterator.current.php
|
357 |
+
*
|
358 |
+
* @return array|null
|
359 |
+
*/
|
360 |
+
public function current()
|
361 |
+
{
|
362 |
+
$rowDataForRowToBeProcessed = $this->rowDataBuffer;
|
363 |
+
|
364 |
+
if ($this->shouldPreserveEmptyRows) {
|
365 |
+
// when we need to preserve empty rows, we will either return
|
366 |
+
// an empty row or the last row read. This depends whether the
|
367 |
+
// index of last row that was read matches the index of the last
|
368 |
+
// row whose value should be returned.
|
369 |
+
if ($this->lastRowIndexProcessed !== $this->nextRowIndexToBeProcessed) {
|
370 |
+
// return empty row if mismatch between last processed row
|
371 |
+
// and the row that needs to be returned
|
372 |
+
$rowDataForRowToBeProcessed = [''];
|
373 |
+
}
|
374 |
+
}
|
375 |
+
|
376 |
+
return $rowDataForRowToBeProcessed;
|
377 |
+
}
|
378 |
+
|
379 |
+
/**
|
380 |
+
* Return the key of the current element. Here, the row index.
|
381 |
+
* @link http://php.net/manual/en/iterator.key.php
|
382 |
+
*
|
383 |
+
* @return int
|
384 |
+
*/
|
385 |
+
public function key()
|
386 |
+
{
|
387 |
+
// TODO: This should return $this->nextRowIndexToBeProcessed
|
388 |
+
// but to avoid a breaking change, the return value for
|
389 |
+
// this function has been kept as the number of rows read.
|
390 |
+
return $this->shouldPreserveEmptyRows ?
|
391 |
+
$this->nextRowIndexToBeProcessed :
|
392 |
+
$this->numReadRows;
|
393 |
+
}
|
394 |
+
|
395 |
+
|
396 |
+
/**
|
397 |
+
* Cleans up what was created to iterate over the object.
|
398 |
+
*
|
399 |
+
* @return void
|
400 |
+
*/
|
401 |
+
public function end()
|
402 |
+
{
|
403 |
+
$this->xmlReader->close();
|
404 |
+
}
|
405 |
+
}
|
app/Services/Spout/Reader/XLSX/Sheet.php
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\SheetInterface;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class Sheet
|
9 |
+
* Represents a sheet within a XLSX file
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Reader\XLSX
|
12 |
+
*/
|
13 |
+
class Sheet implements SheetInterface
|
14 |
+
{
|
15 |
+
/** @var \Box\Spout\Reader\XLSX\RowIterator To iterate over sheet's rows */
|
16 |
+
protected $rowIterator;
|
17 |
+
|
18 |
+
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
19 |
+
protected $index;
|
20 |
+
|
21 |
+
/** @var string Name of the sheet */
|
22 |
+
protected $name;
|
23 |
+
|
24 |
+
/** @var bool Whether the sheet was the active one */
|
25 |
+
protected $isActive;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @param string $filePath Path of the XLSX file being read
|
29 |
+
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
|
30 |
+
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
31 |
+
* @param string $sheetName Name of the sheet
|
32 |
+
* @param bool $isSheetActive Whether the sheet was defined as active
|
33 |
+
* @param \Box\Spout\Reader\XLSX\ReaderOptions $options Reader's current options
|
34 |
+
* @param Helper\SharedStringsHelper Helper to work with shared strings
|
35 |
+
*/
|
36 |
+
public function __construct($filePath, $sheetDataXMLFilePath, $sheetIndex, $sheetName, $isSheetActive, $options, $sharedStringsHelper)
|
37 |
+
{
|
38 |
+
$this->rowIterator = new RowIterator($filePath, $sheetDataXMLFilePath, $options, $sharedStringsHelper);
|
39 |
+
$this->index = $sheetIndex;
|
40 |
+
$this->name = $sheetName;
|
41 |
+
$this->isActive = $isSheetActive;
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @api
|
46 |
+
* @return \Box\Spout\Reader\XLSX\RowIterator
|
47 |
+
*/
|
48 |
+
public function getRowIterator()
|
49 |
+
{
|
50 |
+
return $this->rowIterator;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @api
|
55 |
+
* @return int Index of the sheet, based on order in the workbook (zero-based)
|
56 |
+
*/
|
57 |
+
public function getIndex()
|
58 |
+
{
|
59 |
+
return $this->index;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* @api
|
64 |
+
* @return string Name of the sheet
|
65 |
+
*/
|
66 |
+
public function getName()
|
67 |
+
{
|
68 |
+
return $this->name;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* @api
|
73 |
+
* @return bool Whether the sheet was defined as active
|
74 |
+
*/
|
75 |
+
public function isActive()
|
76 |
+
{
|
77 |
+
return $this->isActive;
|
78 |
+
}
|
79 |
+
}
|
app/Services/Spout/Reader/XLSX/SheetIterator.php
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Reader\XLSX;
|
4 |
+
|
5 |
+
use Box\Spout\Reader\IteratorInterface;
|
6 |
+
use Box\Spout\Reader\XLSX\Helper\SheetHelper;
|
7 |
+
use Box\Spout\Reader\Exception\NoSheetsFoundException;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class SheetIterator
|
11 |
+
* Iterate over XLSX sheet.
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Reader\XLSX
|
14 |
+
*/
|
15 |
+
class SheetIterator implements IteratorInterface
|
16 |
+
{
|
17 |
+
/** @var \Box\Spout\Reader\XLSX\Sheet[] The list of sheet present in the file */
|
18 |
+
protected $sheets;
|
19 |
+
|
20 |
+
/** @var int The index of the sheet being read (zero-based) */
|
21 |
+
protected $currentSheetIndex;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* @param string $filePath Path of the file to be read
|
25 |
+
* @param \Box\Spout\Reader\XLSX\ReaderOptions $options Reader's current options
|
26 |
+
* @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper $sharedStringsHelper
|
27 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
28 |
+
* @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
|
29 |
+
*/
|
30 |
+
public function __construct($filePath, $options, $sharedStringsHelper, $globalFunctionsHelper)
|
31 |
+
{
|
32 |
+
// Fetch all available sheets
|
33 |
+
$sheetHelper = new SheetHelper($filePath, $options, $sharedStringsHelper, $globalFunctionsHelper);
|
34 |
+
$this->sheets = $sheetHelper->getSheets();
|
35 |
+
|
36 |
+
if (count($this->sheets) === 0) {
|
37 |
+
throw new NoSheetsFoundException('The file must contain at least one sheet.');
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Rewind the Iterator to the first element
|
43 |
+
* @link http://php.net/manual/en/iterator.rewind.php
|
44 |
+
*
|
45 |
+
* @return void
|
46 |
+
*/
|
47 |
+
public function rewind()
|
48 |
+
{
|
49 |
+
$this->currentSheetIndex = 0;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Checks if current position is valid
|
54 |
+
* @link http://php.net/manual/en/iterator.valid.php
|
55 |
+
*
|
56 |
+
* @return bool
|
57 |
+
*/
|
58 |
+
public function valid()
|
59 |
+
{
|
60 |
+
return ($this->currentSheetIndex < count($this->sheets));
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Move forward to next element
|
65 |
+
* @link http://php.net/manual/en/iterator.next.php
|
66 |
+
*
|
67 |
+
* @return void
|
68 |
+
*/
|
69 |
+
public function next()
|
70 |
+
{
|
71 |
+
// Using isset here because it is way faster than array_key_exists...
|
72 |
+
if (isset($this->sheets[$this->currentSheetIndex])) {
|
73 |
+
$currentSheet = $this->sheets[$this->currentSheetIndex];
|
74 |
+
$currentSheet->getRowIterator()->end();
|
75 |
+
|
76 |
+
$this->currentSheetIndex++;
|
77 |
+
}
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Return the current element
|
82 |
+
* @link http://php.net/manual/en/iterator.current.php
|
83 |
+
*
|
84 |
+
* @return \Box\Spout\Reader\XLSX\Sheet
|
85 |
+
*/
|
86 |
+
public function current()
|
87 |
+
{
|
88 |
+
return $this->sheets[$this->currentSheetIndex];
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Return the key of the current element
|
93 |
+
* @link http://php.net/manual/en/iterator.key.php
|
94 |
+
*
|
95 |
+
* @return int
|
96 |
+
*/
|
97 |
+
public function key()
|
98 |
+
{
|
99 |
+
return $this->currentSheetIndex + 1;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Cleans up what was created to iterate over the object.
|
104 |
+
*
|
105 |
+
* @return void
|
106 |
+
*/
|
107 |
+
public function end()
|
108 |
+
{
|
109 |
+
// make sure we are not leaking memory in case the iteration stopped before the end
|
110 |
+
foreach ($this->sheets as $sheet) {
|
111 |
+
$sheet->getRowIterator()->end();
|
112 |
+
}
|
113 |
+
}
|
114 |
+
}
|
app/Services/Spout/Writer/AbstractMultiSheetsWriter.php
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\WriterNotOpenedException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class AbstractMultiSheetsWriter
|
9 |
+
*
|
10 |
+
* @package Box\Spout\Writer
|
11 |
+
* @abstract
|
12 |
+
*/
|
13 |
+
abstract class AbstractMultiSheetsWriter extends AbstractWriter
|
14 |
+
{
|
15 |
+
/** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */
|
16 |
+
protected $shouldCreateNewSheetsAutomatically = true;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* @return Common\Internal\WorkbookInterface The workbook representing the file to be written
|
20 |
+
*/
|
21 |
+
abstract protected function getWorkbook();
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Sets whether new sheets should be automatically created when the max rows limit per sheet is reached.
|
25 |
+
* This must be set before opening the writer.
|
26 |
+
*
|
27 |
+
* @api
|
28 |
+
* @param bool $shouldCreateNewSheetsAutomatically Whether new sheets should be automatically created when the max rows limit per sheet is reached
|
29 |
+
* @return AbstractMultiSheetsWriter
|
30 |
+
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
|
31 |
+
*/
|
32 |
+
public function setShouldCreateNewSheetsAutomatically($shouldCreateNewSheetsAutomatically)
|
33 |
+
{
|
34 |
+
$this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
|
35 |
+
|
36 |
+
$this->shouldCreateNewSheetsAutomatically = $shouldCreateNewSheetsAutomatically;
|
37 |
+
return $this;
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Returns all the workbook's sheets
|
42 |
+
*
|
43 |
+
* @api
|
44 |
+
* @return Common\Sheet[] All the workbook's sheets
|
45 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
46 |
+
*/
|
47 |
+
public function getSheets()
|
48 |
+
{
|
49 |
+
$this->throwIfBookIsNotAvailable();
|
50 |
+
|
51 |
+
$externalSheets = [];
|
52 |
+
$worksheets = $this->getWorkbook()->getWorksheets();
|
53 |
+
|
54 |
+
/** @var Common\Internal\WorksheetInterface $worksheet */
|
55 |
+
foreach ($worksheets as $worksheet) {
|
56 |
+
$externalSheets[] = $worksheet->getExternalSheet();
|
57 |
+
}
|
58 |
+
|
59 |
+
return $externalSheets;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Creates a new sheet and make it the current sheet. The data will now be written to this sheet.
|
64 |
+
*
|
65 |
+
* @api
|
66 |
+
* @return Common\Sheet The created sheet
|
67 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
68 |
+
*/
|
69 |
+
public function addNewSheetAndMakeItCurrent()
|
70 |
+
{
|
71 |
+
$this->throwIfBookIsNotAvailable();
|
72 |
+
$worksheet = $this->getWorkbook()->addNewSheetAndMakeItCurrent();
|
73 |
+
|
74 |
+
return $worksheet->getExternalSheet();
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Returns the current sheet
|
79 |
+
*
|
80 |
+
* @api
|
81 |
+
* @return Common\Sheet The current sheet
|
82 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
83 |
+
*/
|
84 |
+
public function getCurrentSheet()
|
85 |
+
{
|
86 |
+
$this->throwIfBookIsNotAvailable();
|
87 |
+
return $this->getWorkbook()->getCurrentWorksheet()->getExternalSheet();
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Sets the given sheet as the current one. New data will be written to this sheet.
|
92 |
+
* The writing will resume where it stopped (i.e. data won't be truncated).
|
93 |
+
*
|
94 |
+
* @api
|
95 |
+
* @param Common\Sheet $sheet The sheet to set as current
|
96 |
+
* @return void
|
97 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
98 |
+
* @throws \Box\Spout\Writer\Exception\SheetNotFoundException If the given sheet does not exist in the workbook
|
99 |
+
*/
|
100 |
+
public function setCurrentSheet($sheet)
|
101 |
+
{
|
102 |
+
$this->throwIfBookIsNotAvailable();
|
103 |
+
$this->getWorkbook()->setCurrentSheet($sheet);
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Checks if the book has been created. Throws an exception if not created yet.
|
108 |
+
*
|
109 |
+
* @return void
|
110 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the book is not created yet
|
111 |
+
*/
|
112 |
+
protected function throwIfBookIsNotAvailable()
|
113 |
+
{
|
114 |
+
if (!$this->getWorkbook()) {
|
115 |
+
throw new WriterNotOpenedException('The writer must be opened before performing this action.');
|
116 |
+
}
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
app/Services/Spout/Writer/AbstractWriter.php
ADDED
@@ -0,0 +1,384 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
use Box\Spout\Common\Exception\InvalidArgumentException;
|
7 |
+
use Box\Spout\Common\Exception\SpoutException;
|
8 |
+
use Box\Spout\Common\Helper\FileSystemHelper;
|
9 |
+
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
|
10 |
+
use Box\Spout\Writer\Exception\WriterNotOpenedException;
|
11 |
+
use Box\Spout\Writer\Style\StyleBuilder;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Class AbstractWriter
|
15 |
+
*
|
16 |
+
* @package Box\Spout\Writer
|
17 |
+
* @abstract
|
18 |
+
*/
|
19 |
+
abstract class AbstractWriter implements WriterInterface
|
20 |
+
{
|
21 |
+
/** @var string Path to the output file */
|
22 |
+
protected $outputFilePath;
|
23 |
+
|
24 |
+
/** @var resource Pointer to the file/stream we will write to */
|
25 |
+
protected $filePointer;
|
26 |
+
|
27 |
+
/** @var bool Indicates whether the writer has been opened or not */
|
28 |
+
protected $isWriterOpened = false;
|
29 |
+
|
30 |
+
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
31 |
+
protected $globalFunctionsHelper;
|
32 |
+
|
33 |
+
/** @var Style\Style Style to be applied to the next written row(s) */
|
34 |
+
protected $rowStyle;
|
35 |
+
|
36 |
+
/** @var Style\Style Default row style. Each writer can have its own default style */
|
37 |
+
protected $defaultRowStyle;
|
38 |
+
|
39 |
+
/** @var string Content-Type value for the header - to be defined by child class */
|
40 |
+
protected static $headerContentType;
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Opens the streamer and makes it ready to accept data.
|
44 |
+
*
|
45 |
+
* @return void
|
46 |
+
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
|
47 |
+
*/
|
48 |
+
abstract protected function openWriter();
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Adds data to the currently openned writer.
|
52 |
+
*
|
53 |
+
* @param array $dataRow Array containing data to be streamed.
|
54 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
55 |
+
* @param Style\Style $style Style to be applied to the written row
|
56 |
+
* @return void
|
57 |
+
*/
|
58 |
+
abstract protected function addRowToWriter(array $dataRow, $style);
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Closes the streamer, preventing any additional writing.
|
62 |
+
*
|
63 |
+
* @return void
|
64 |
+
*/
|
65 |
+
abstract protected function closeWriter();
|
66 |
+
|
67 |
+
/**
|
68 |
+
*
|
69 |
+
*/
|
70 |
+
public function __construct()
|
71 |
+
{
|
72 |
+
$this->defaultRowStyle = $this->getDefaultRowStyle();
|
73 |
+
$this->resetRowStyleToDefault();
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Sets the default styles for all rows added with "addRow".
|
78 |
+
* Overriding the default style instead of using "addRowWithStyle" improves performance by 20%.
|
79 |
+
* @see https://github.com/box/spout/issues/272
|
80 |
+
*
|
81 |
+
* @param Style\Style $defaultStyle
|
82 |
+
* @return AbstractWriter
|
83 |
+
*/
|
84 |
+
public function setDefaultRowStyle($defaultStyle)
|
85 |
+
{
|
86 |
+
$this->defaultRowStyle = $defaultStyle;
|
87 |
+
$this->resetRowStyleToDefault();
|
88 |
+
return $this;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
93 |
+
* @return AbstractWriter
|
94 |
+
*/
|
95 |
+
public function setGlobalFunctionsHelper($globalFunctionsHelper)
|
96 |
+
{
|
97 |
+
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
98 |
+
return $this;
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Inits the writer and opens it to accept data.
|
103 |
+
* By using this method, the data will be written to a file.
|
104 |
+
*
|
105 |
+
* @api
|
106 |
+
* @param string $outputFilePath Path of the output file that will contain the data
|
107 |
+
* @return AbstractWriter
|
108 |
+
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
|
109 |
+
*/
|
110 |
+
public function openToFile($outputFilePath)
|
111 |
+
{
|
112 |
+
$this->outputFilePath = $outputFilePath;
|
113 |
+
|
114 |
+
$this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
|
115 |
+
$this->throwIfFilePointerIsNotAvailable();
|
116 |
+
|
117 |
+
$this->openWriter();
|
118 |
+
$this->isWriterOpened = true;
|
119 |
+
|
120 |
+
return $this;
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Inits the writer and opens it to accept data.
|
125 |
+
* By using this method, the data will be outputted directly to the browser.
|
126 |
+
*
|
127 |
+
* @codeCoverageIgnore
|
128 |
+
*
|
129 |
+
* @api
|
130 |
+
* @param string $outputFileName Name of the output file that will contain the data. If a path is passed in, only the file name will be kept
|
131 |
+
* @return AbstractWriter
|
132 |
+
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
|
133 |
+
*/
|
134 |
+
public function openToBrowser($outputFileName)
|
135 |
+
{
|
136 |
+
$this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
|
137 |
+
|
138 |
+
$this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
|
139 |
+
$this->throwIfFilePointerIsNotAvailable();
|
140 |
+
|
141 |
+
// Clear any previous output (otherwise the generated file will be corrupted)
|
142 |
+
// @see https://github.com/box/spout/issues/241
|
143 |
+
$this->globalFunctionsHelper->ob_end_clean();
|
144 |
+
|
145 |
+
// Set headers
|
146 |
+
$this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
|
147 |
+
$this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
|
148 |
+
|
149 |
+
/*
|
150 |
+
* When forcing the download of a file over SSL,IE8 and lower browsers fail
|
151 |
+
* if the Cache-Control and Pragma headers are not set.
|
152 |
+
*
|
153 |
+
* @see http://support.microsoft.com/KB/323308
|
154 |
+
* @see https://github.com/liuggio/ExcelBundle/issues/45
|
155 |
+
*/
|
156 |
+
$this->globalFunctionsHelper->header('Cache-Control: max-age=0');
|
157 |
+
$this->globalFunctionsHelper->header('Pragma: public');
|
158 |
+
|
159 |
+
$this->openWriter();
|
160 |
+
$this->isWriterOpened = true;
|
161 |
+
|
162 |
+
return $this;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Checks if the pointer to the file/stream to write to is available.
|
167 |
+
* Will throw an exception if not available.
|
168 |
+
*
|
169 |
+
* @return void
|
170 |
+
* @throws \Box\Spout\Common\Exception\IOException If the pointer is not available
|
171 |
+
*/
|
172 |
+
protected function throwIfFilePointerIsNotAvailable()
|
173 |
+
{
|
174 |
+
if (!$this->filePointer) {
|
175 |
+
throw new IOException('File pointer has not be opened');
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
/**
|
180 |
+
* Checks if the writer has already been opened, since some actions must be done before it gets opened.
|
181 |
+
* Throws an exception if already opened.
|
182 |
+
*
|
183 |
+
* @param string $message Error message
|
184 |
+
* @return void
|
185 |
+
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened and must not be.
|
186 |
+
*/
|
187 |
+
protected function throwIfWriterAlreadyOpened($message)
|
188 |
+
{
|
189 |
+
if ($this->isWriterOpened) {
|
190 |
+
throw new WriterAlreadyOpenedException($message);
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
/**
|
195 |
+
* Write given data to the output. New data will be appended to end of stream.
|
196 |
+
*
|
197 |
+
* @param array $dataRow Array containing data to be streamed.
|
198 |
+
* If empty, no data is added (i.e. not even as a blank row)
|
199 |
+
* Example: $dataRow = ['data1', 1234, null, '', 'data5', false];
|
200 |
+
* @api
|
201 |
+
* @return AbstractWriter
|
202 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
203 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
204 |
+
* @throws \Box\Spout\Common\Exception\SpoutException If anything else goes wrong while writing data
|
205 |
+
*/
|
206 |
+
public function addRow(array $dataRow)
|
207 |
+
{
|
208 |
+
if ($this->isWriterOpened) {
|
209 |
+
// empty $dataRow should not add an empty line
|
210 |
+
if (!empty($dataRow)) {
|
211 |
+
try {
|
212 |
+
$this->addRowToWriter($dataRow, $this->rowStyle);
|
213 |
+
} catch (SpoutException $e) {
|
214 |
+
// if an exception occurs while writing data,
|
215 |
+
// close the writer and remove all files created so far.
|
216 |
+
$this->closeAndAttemptToCleanupAllFiles();
|
217 |
+
|
218 |
+
// re-throw the exception to alert developers of the error
|
219 |
+
throw $e;
|
220 |
+
}
|
221 |
+
}
|
222 |
+
} else {
|
223 |
+
throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
|
224 |
+
}
|
225 |
+
|
226 |
+
return $this;
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Write given data to the output and apply the given style.
|
231 |
+
* @see addRow
|
232 |
+
*
|
233 |
+
* @api
|
234 |
+
* @param array $dataRow Array of array containing data to be streamed.
|
235 |
+
* @param Style\Style $style Style to be applied to the row.
|
236 |
+
* @return AbstractWriter
|
237 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
238 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
239 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
240 |
+
*/
|
241 |
+
public function addRowWithStyle(array $dataRow, $style)
|
242 |
+
{
|
243 |
+
if (!$style instanceof Style\Style) {
|
244 |
+
throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.');
|
245 |
+
}
|
246 |
+
|
247 |
+
$this->setRowStyle($style);
|
248 |
+
$this->addRow($dataRow);
|
249 |
+
$this->resetRowStyleToDefault();
|
250 |
+
|
251 |
+
return $this;
|
252 |
+
}
|
253 |
+
|
254 |
+
/**
|
255 |
+
* Write given data to the output. New data will be appended to end of stream.
|
256 |
+
*
|
257 |
+
* @api
|
258 |
+
* @param array $dataRows Array of array containing data to be streamed.
|
259 |
+
* If a row is empty, it won't be added (i.e. not even as a blank row)
|
260 |
+
* Example: $dataRows = [
|
261 |
+
* ['data11', 12, , '', 'data13'],
|
262 |
+
* ['data21', 'data22', null, false],
|
263 |
+
* ];
|
264 |
+
* @return AbstractWriter
|
265 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
266 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
267 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
268 |
+
*/
|
269 |
+
public function addRows(array $dataRows)
|
270 |
+
{
|
271 |
+
if (!empty($dataRows)) {
|
272 |
+
$firstRow = reset($dataRows);
|
273 |
+
if (!is_array($firstRow)) {
|
274 |
+
throw new InvalidArgumentException('The input should be an array of arrays');
|
275 |
+
}
|
276 |
+
|
277 |
+
foreach ($dataRows as $dataRow) {
|
278 |
+
$this->addRow($dataRow);
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
return $this;
|
283 |
+
}
|
284 |
+
|
285 |
+
/**
|
286 |
+
* Write given data to the output and apply the given style.
|
287 |
+
* @see addRows
|
288 |
+
*
|
289 |
+
* @api
|
290 |
+
* @param array $dataRows Array of array containing data to be streamed.
|
291 |
+
* @param Style\Style $style Style to be applied to the rows.
|
292 |
+
* @return AbstractWriter
|
293 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
294 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
295 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
296 |
+
*/
|
297 |
+
public function addRowsWithStyle(array $dataRows, $style)
|
298 |
+
{
|
299 |
+
if (!$style instanceof Style\Style) {
|
300 |
+
throw new InvalidArgumentException('The "$style" argument must be a Style instance and cannot be NULL.');
|
301 |
+
}
|
302 |
+
|
303 |
+
$this->setRowStyle($style);
|
304 |
+
$this->addRows($dataRows);
|
305 |
+
$this->resetRowStyleToDefault();
|
306 |
+
|
307 |
+
return $this;
|
308 |
+
}
|
309 |
+
|
310 |
+
/**
|
311 |
+
* Returns the default style to be applied to rows.
|
312 |
+
* Can be overriden by children to have a custom style.
|
313 |
+
*
|
314 |
+
* @return Style\Style
|
315 |
+
*/
|
316 |
+
protected function getDefaultRowStyle()
|
317 |
+
{
|
318 |
+
return (new StyleBuilder())->build();
|
319 |
+
}
|
320 |
+
|
321 |
+
/**
|
322 |
+
* Sets the style to be applied to the next written rows
|
323 |
+
* until it is changed or reset.
|
324 |
+
*
|
325 |
+
* @param Style\Style $style
|
326 |
+
* @return void
|
327 |
+
*/
|
328 |
+
private function setRowStyle($style)
|
329 |
+
{
|
330 |
+
// Merge given style with the default one to inherit custom properties
|
331 |
+
$this->rowStyle = $style->mergeWith($this->defaultRowStyle);
|
332 |
+
}
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Resets the style to be applied to the next written rows.
|
336 |
+
*
|
337 |
+
* @return void
|
338 |
+
*/
|
339 |
+
private function resetRowStyleToDefault()
|
340 |
+
{
|
341 |
+
$this->rowStyle = $this->defaultRowStyle;
|
342 |
+
}
|
343 |
+
|
344 |
+
/**
|
345 |
+
* Closes the writer. This will close the streamer as well, preventing new data
|
346 |
+
* to be written to the file.
|
347 |
+
*
|
348 |
+
* @api
|
349 |
+
* @return void
|
350 |
+
*/
|
351 |
+
public function close()
|
352 |
+
{
|
353 |
+
if (!$this->isWriterOpened) {
|
354 |
+
return;
|
355 |
+
}
|
356 |
+
|
357 |
+
$this->closeWriter();
|
358 |
+
|
359 |
+
if (is_resource($this->filePointer)) {
|
360 |
+
$this->globalFunctionsHelper->fclose($this->filePointer);
|
361 |
+
}
|
362 |
+
|
363 |
+
$this->isWriterOpened = false;
|
364 |
+
}
|
365 |
+
|
366 |
+
/**
|
367 |
+
* Closes the writer and attempts to cleanup all files that were
|
368 |
+
* created during the writing process (temp files & final file).
|
369 |
+
*
|
370 |
+
* @return void
|
371 |
+
*/
|
372 |
+
private function closeAndAttemptToCleanupAllFiles()
|
373 |
+
{
|
374 |
+
// close the writer, which should remove all temp files
|
375 |
+
$this->close();
|
376 |
+
|
377 |
+
// remove output file if it was created
|
378 |
+
if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
|
379 |
+
$outputFolderPath = dirname($this->outputFilePath);
|
380 |
+
$fileSystemHelper = new FileSystemHelper($outputFolderPath);
|
381 |
+
$fileSystemHelper->deleteFile($this->outputFilePath);
|
382 |
+
}
|
383 |
+
}
|
384 |
+
}
|
app/Services/Spout/Writer/CSV/Writer.php
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\CSV;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\AbstractWriter;
|
6 |
+
use Box\Spout\Common\Exception\IOException;
|
7 |
+
use Box\Spout\Common\Helper\EncodingHelper;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class Writer
|
11 |
+
* This class provides support to write data to CSV files
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Writer\CSV
|
14 |
+
*/
|
15 |
+
class Writer extends AbstractWriter
|
16 |
+
{
|
17 |
+
/** Number of rows to write before flushing */
|
18 |
+
const FLUSH_THRESHOLD = 500;
|
19 |
+
|
20 |
+
/** @var string Content-Type value for the header */
|
21 |
+
protected static $headerContentType = 'text/csv; charset=UTF-8';
|
22 |
+
|
23 |
+
/** @var string Defines the character used to delimit fields (one character only) */
|
24 |
+
protected $fieldDelimiter = ',';
|
25 |
+
|
26 |
+
/** @var string Defines the character used to enclose fields (one character only) */
|
27 |
+
protected $fieldEnclosure = '"';
|
28 |
+
|
29 |
+
/** @var int */
|
30 |
+
protected $lastWrittenRowIndex = 0;
|
31 |
+
|
32 |
+
/** @var bool */
|
33 |
+
protected $shouldAddBOM = true;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Sets the field delimiter for the CSV
|
37 |
+
*
|
38 |
+
* @api
|
39 |
+
* @param string $fieldDelimiter Character that delimits fields
|
40 |
+
* @return Writer
|
41 |
+
*/
|
42 |
+
public function setFieldDelimiter($fieldDelimiter)
|
43 |
+
{
|
44 |
+
$this->fieldDelimiter = $fieldDelimiter;
|
45 |
+
return $this;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Sets the field enclosure for the CSV
|
50 |
+
*
|
51 |
+
* @api
|
52 |
+
* @param string $fieldEnclosure Character that enclose fields
|
53 |
+
* @return Writer
|
54 |
+
*/
|
55 |
+
public function setFieldEnclosure($fieldEnclosure)
|
56 |
+
{
|
57 |
+
$this->fieldEnclosure = $fieldEnclosure;
|
58 |
+
return $this;
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Set if a BOM has to be added to the file
|
63 |
+
*
|
64 |
+
* @param bool $shouldAddBOM
|
65 |
+
* @return Writer
|
66 |
+
*/
|
67 |
+
public function setShouldAddBOM($shouldAddBOM)
|
68 |
+
{
|
69 |
+
$this->shouldAddBOM = (bool) $shouldAddBOM;
|
70 |
+
return $this;
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Opens the CSV streamer and makes it ready to accept data.
|
75 |
+
*
|
76 |
+
* @return void
|
77 |
+
*/
|
78 |
+
protected function openWriter()
|
79 |
+
{
|
80 |
+
if ($this->shouldAddBOM) {
|
81 |
+
// Adds UTF-8 BOM for Unicode compatibility
|
82 |
+
$this->globalFunctionsHelper->fputs($this->filePointer, EncodingHelper::BOM_UTF8);
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Adds data to the currently opened writer.
|
88 |
+
*
|
89 |
+
* @param array $dataRow Array containing data to be written.
|
90 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
91 |
+
* @param \Box\Spout\Writer\Style\Style $style Ignored here since CSV does not support styling.
|
92 |
+
* @return void
|
93 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
94 |
+
*/
|
95 |
+
protected function addRowToWriter(array $dataRow, $style)
|
96 |
+
{
|
97 |
+
$wasWriteSuccessful = $this->globalFunctionsHelper->fputcsv($this->filePointer, $dataRow, $this->fieldDelimiter, $this->fieldEnclosure);
|
98 |
+
if ($wasWriteSuccessful === false) {
|
99 |
+
throw new IOException('Unable to write data');
|
100 |
+
}
|
101 |
+
|
102 |
+
$this->lastWrittenRowIndex++;
|
103 |
+
if ($this->lastWrittenRowIndex % self::FLUSH_THRESHOLD === 0) {
|
104 |
+
$this->globalFunctionsHelper->fflush($this->filePointer);
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Closes the CSV streamer, preventing any additional writing.
|
110 |
+
* If set, sets the headers and redirects output to the browser.
|
111 |
+
*
|
112 |
+
* @return void
|
113 |
+
*/
|
114 |
+
protected function closeWriter()
|
115 |
+
{
|
116 |
+
$this->lastWrittenRowIndex = 0;
|
117 |
+
}
|
118 |
+
}
|
app/Services/Spout/Writer/Common/Helper/AbstractStyleHelper.php
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class AbstractStyleHelper
|
7 |
+
* This class provides helper functions to manage styles
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Writer\Common\Helper
|
10 |
+
*/
|
11 |
+
abstract class AbstractStyleHelper
|
12 |
+
{
|
13 |
+
/** @var array [SERIALIZED_STYLE] => [STYLE_ID] mapping table, keeping track of the registered styles */
|
14 |
+
protected $serializedStyleToStyleIdMappingTable = [];
|
15 |
+
|
16 |
+
/** @var array [STYLE_ID] => [STYLE] mapping table, keeping track of the registered styles */
|
17 |
+
protected $styleIdToStyleMappingTable = [];
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @param \Box\Spout\Writer\Style\Style $defaultStyle
|
21 |
+
*/
|
22 |
+
public function __construct($defaultStyle)
|
23 |
+
{
|
24 |
+
// This ensures that the default style is the first one to be registered
|
25 |
+
$this->registerStyle($defaultStyle);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Registers the given style as a used style.
|
30 |
+
* Duplicate styles won't be registered more than once.
|
31 |
+
*
|
32 |
+
* @param \Box\Spout\Writer\Style\Style $style The style to be registered
|
33 |
+
* @return \Box\Spout\Writer\Style\Style The registered style, updated with an internal ID.
|
34 |
+
*/
|
35 |
+
public function registerStyle($style)
|
36 |
+
{
|
37 |
+
$serializedStyle = $style->serialize();
|
38 |
+
|
39 |
+
if (!$this->hasStyleAlreadyBeenRegistered($style)) {
|
40 |
+
$nextStyleId = count($this->serializedStyleToStyleIdMappingTable);
|
41 |
+
$style->setId($nextStyleId);
|
42 |
+
|
43 |
+
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
|
44 |
+
$this->styleIdToStyleMappingTable[$nextStyleId] = $style;
|
45 |
+
}
|
46 |
+
|
47 |
+
return $this->getStyleFromSerializedStyle($serializedStyle);
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Returns whether the given style has already been registered.
|
52 |
+
*
|
53 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
54 |
+
* @return bool
|
55 |
+
*/
|
56 |
+
protected function hasStyleAlreadyBeenRegistered($style)
|
57 |
+
{
|
58 |
+
$serializedStyle = $style->serialize();
|
59 |
+
|
60 |
+
// Using isset here because it is way faster than array_key_exists...
|
61 |
+
return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Returns the registered style associated to the given serialization.
|
66 |
+
*
|
67 |
+
* @param string $serializedStyle The serialized style from which the actual style should be fetched from
|
68 |
+
* @return \Box\Spout\Writer\Style\Style
|
69 |
+
*/
|
70 |
+
protected function getStyleFromSerializedStyle($serializedStyle)
|
71 |
+
{
|
72 |
+
$styleId = $this->serializedStyleToStyleIdMappingTable[$serializedStyle];
|
73 |
+
return $this->styleIdToStyleMappingTable[$styleId];
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* @return \Box\Spout\Writer\Style\Style[] List of registered styles
|
78 |
+
*/
|
79 |
+
protected function getRegisteredStyles()
|
80 |
+
{
|
81 |
+
return array_values($this->styleIdToStyleMappingTable);
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Returns the default style
|
86 |
+
*
|
87 |
+
* @return \Box\Spout\Writer\Style\Style Default style
|
88 |
+
*/
|
89 |
+
protected function getDefaultStyle()
|
90 |
+
{
|
91 |
+
// By construction, the default style has ID 0
|
92 |
+
return $this->styleIdToStyleMappingTable[0];
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Apply additional styles if the given row needs it.
|
97 |
+
* Typically, set "wrap text" if a cell contains a new line.
|
98 |
+
*
|
99 |
+
* @param \Box\Spout\Writer\Style\Style $style The original style
|
100 |
+
* @param array $dataRow The row the style will be applied to
|
101 |
+
* @return \Box\Spout\Writer\Style\Style The updated style
|
102 |
+
*/
|
103 |
+
public function applyExtraStylesIfNeeded($style, $dataRow)
|
104 |
+
{
|
105 |
+
$updatedStyle = $this->applyWrapTextIfCellContainsNewLine($style, $dataRow);
|
106 |
+
return $updatedStyle;
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Set the "wrap text" option if a cell of the given row contains a new line.
|
111 |
+
*
|
112 |
+
* @NOTE: There is a bug on the Mac version of Excel (2011 and below) where new lines
|
113 |
+
* are ignored even when the "wrap text" option is set. This only occurs with
|
114 |
+
* inline strings (shared strings do work fine).
|
115 |
+
* A workaround would be to encode "\n" as "_x000D_" but it does not work
|
116 |
+
* on the Windows version of Excel...
|
117 |
+
*
|
118 |
+
* @param \Box\Spout\Writer\Style\Style $style The original style
|
119 |
+
* @param array $dataRow The row the style will be applied to
|
120 |
+
* @return \Box\Spout\Writer\Style\Style The eventually updated style
|
121 |
+
*/
|
122 |
+
protected function applyWrapTextIfCellContainsNewLine($style, $dataRow)
|
123 |
+
{
|
124 |
+
// if the "wrap text" option is already set, no-op
|
125 |
+
if ($style->hasSetWrapText()) {
|
126 |
+
return $style;
|
127 |
+
}
|
128 |
+
|
129 |
+
foreach ($dataRow as $cell) {
|
130 |
+
if (is_string($cell) && strpos($cell, "\n") !== false) {
|
131 |
+
$style->setShouldWrapText();
|
132 |
+
break;
|
133 |
+
}
|
134 |
+
}
|
135 |
+
|
136 |
+
return $style;
|
137 |
+
}
|
138 |
+
}
|
app/Services/Spout/Writer/Common/Helper/CellHelper.php
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class CellHelper
|
7 |
+
* This class provides helper functions when working with cells
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Writer\Common\Helper
|
10 |
+
*/
|
11 |
+
class CellHelper
|
12 |
+
{
|
13 |
+
/** @var array Cache containing the mapping column index => cell index */
|
14 |
+
private static $columnIndexToCellIndexCache = [];
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Returns the cell index (base 26) associated to the base 10 column index.
|
18 |
+
* Excel uses A to Z letters for column indexing, where A is the 1st column,
|
19 |
+
* Z is the 26th and AA is the 27th.
|
20 |
+
* The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
|
21 |
+
*
|
22 |
+
* @param int $columnIndex The Excel column index (0, 42, ...)
|
23 |
+
* @return string The associated cell index ('A', 'BC', ...)
|
24 |
+
*/
|
25 |
+
public static function getCellIndexFromColumnIndex($columnIndex)
|
26 |
+
{
|
27 |
+
$originalColumnIndex = $columnIndex;
|
28 |
+
|
29 |
+
// Using isset here because it is way faster than array_key_exists...
|
30 |
+
if (!isset(self::$columnIndexToCellIndexCache[$originalColumnIndex])) {
|
31 |
+
$cellIndex = '';
|
32 |
+
$capitalAAsciiValue = ord('A');
|
33 |
+
|
34 |
+
do {
|
35 |
+
$modulus = $columnIndex % 26;
|
36 |
+
$cellIndex = chr($capitalAAsciiValue + $modulus) . $cellIndex;
|
37 |
+
|
38 |
+
// substracting 1 because it's zero-based
|
39 |
+
$columnIndex = intval($columnIndex / 26) - 1;
|
40 |
+
|
41 |
+
} while ($columnIndex >= 0);
|
42 |
+
|
43 |
+
self::$columnIndexToCellIndexCache[$originalColumnIndex] = $cellIndex;
|
44 |
+
}
|
45 |
+
|
46 |
+
return self::$columnIndexToCellIndexCache[$originalColumnIndex];
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* @param $value
|
51 |
+
* @return bool Whether the given value is considered "empty"
|
52 |
+
*/
|
53 |
+
public static function isEmpty($value)
|
54 |
+
{
|
55 |
+
return ($value === null || $value === '');
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* @param $value
|
60 |
+
* @return bool Whether the given value is a non empty string
|
61 |
+
*/
|
62 |
+
public static function isNonEmptyString($value)
|
63 |
+
{
|
64 |
+
return (gettype($value) === 'string' && $value !== '');
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Returns whether the given value is numeric.
|
69 |
+
* A numeric value is from type "integer" or "double" ("float" is not returned by gettype).
|
70 |
+
*
|
71 |
+
* @param $value
|
72 |
+
* @return bool Whether the given value is numeric
|
73 |
+
*/
|
74 |
+
public static function isNumeric($value)
|
75 |
+
{
|
76 |
+
$valueType = gettype($value);
|
77 |
+
return ($valueType === 'integer' || $valueType === 'double');
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Returns whether the given value is boolean.
|
82 |
+
* "true"/"false" and 0/1 are not booleans.
|
83 |
+
*
|
84 |
+
* @param $value
|
85 |
+
* @return bool Whether the given value is boolean
|
86 |
+
*/
|
87 |
+
public static function isBoolean($value)
|
88 |
+
{
|
89 |
+
return gettype($value) === 'boolean';
|
90 |
+
}
|
91 |
+
}
|
app/Services/Spout/Writer/Common/Helper/ZipHelper.php
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common\Helper;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class ZipHelper
|
7 |
+
* This class provides helper functions to create zip files
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Writer\Common\Helper
|
10 |
+
*/
|
11 |
+
class ZipHelper
|
12 |
+
{
|
13 |
+
const ZIP_EXTENSION = '.zip';
|
14 |
+
|
15 |
+
/** Controls what to do when trying to add an existing file */
|
16 |
+
const EXISTING_FILES_SKIP = 'skip';
|
17 |
+
const EXISTING_FILES_OVERWRITE = 'overwrite';
|
18 |
+
|
19 |
+
/** @var string Path of the folder where the zip file will be created */
|
20 |
+
protected $tmpFolderPath;
|
21 |
+
|
22 |
+
/** @var \ZipArchive The ZipArchive instance */
|
23 |
+
protected $zip;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @param string $tmpFolderPath Path of the temp folder where the zip file will be created
|
27 |
+
*/
|
28 |
+
public function __construct($tmpFolderPath)
|
29 |
+
{
|
30 |
+
$this->tmpFolderPath = $tmpFolderPath;
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Returns the already created ZipArchive instance or
|
35 |
+
* creates one if none exists.
|
36 |
+
*
|
37 |
+
* @return \ZipArchive
|
38 |
+
*/
|
39 |
+
protected function createOrGetZip()
|
40 |
+
{
|
41 |
+
if (!isset($this->zip)) {
|
42 |
+
$this->zip = new \ZipArchive();
|
43 |
+
$zipFilePath = $this->getZipFilePath();
|
44 |
+
|
45 |
+
$this->zip->open($zipFilePath, \ZipArchive::CREATE|\ZipArchive::OVERWRITE);
|
46 |
+
}
|
47 |
+
|
48 |
+
return $this->zip;
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* @return string Path where the zip file of the given folder will be created
|
53 |
+
*/
|
54 |
+
public function getZipFilePath()
|
55 |
+
{
|
56 |
+
return $this->tmpFolderPath . self::ZIP_EXTENSION;
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Adds the given file, located under the given root folder to the archive.
|
61 |
+
* The file will be compressed.
|
62 |
+
*
|
63 |
+
* Example of use:
|
64 |
+
* addFileToArchive('/tmp/xlsx/foo', 'bar/baz.xml');
|
65 |
+
* => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
|
66 |
+
*
|
67 |
+
* @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
|
68 |
+
* @param string $localFilePath Path of the file to be added, under the root folder
|
69 |
+
* @param string|void $existingFileMode Controls what to do when trying to add an existing file
|
70 |
+
* @return void
|
71 |
+
*/
|
72 |
+
public function addFileToArchive($rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
|
73 |
+
{
|
74 |
+
$this->addFileToArchiveWithCompressionMethod(
|
75 |
+
$rootFolderPath,
|
76 |
+
$localFilePath,
|
77 |
+
$existingFileMode,
|
78 |
+
\ZipArchive::CM_DEFAULT
|
79 |
+
);
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Adds the given file, located under the given root folder to the archive.
|
84 |
+
* The file will NOT be compressed.
|
85 |
+
*
|
86 |
+
* Example of use:
|
87 |
+
* addUncompressedFileToArchive('/tmp/xlsx/foo', 'bar/baz.xml');
|
88 |
+
* => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
|
89 |
+
*
|
90 |
+
* @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
|
91 |
+
* @param string $localFilePath Path of the file to be added, under the root folder
|
92 |
+
* @param string|void $existingFileMode Controls what to do when trying to add an existing file
|
93 |
+
* @return void
|
94 |
+
*/
|
95 |
+
public function addUncompressedFileToArchive($rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
|
96 |
+
{
|
97 |
+
$this->addFileToArchiveWithCompressionMethod(
|
98 |
+
$rootFolderPath,
|
99 |
+
$localFilePath,
|
100 |
+
$existingFileMode,
|
101 |
+
\ZipArchive::CM_STORE
|
102 |
+
);
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Adds the given file, located under the given root folder to the archive.
|
107 |
+
* The file will NOT be compressed.
|
108 |
+
*
|
109 |
+
* Example of use:
|
110 |
+
* addUncompressedFileToArchive('/tmp/xlsx/foo', 'bar/baz.xml');
|
111 |
+
* => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
|
112 |
+
*
|
113 |
+
* @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
|
114 |
+
* @param string $localFilePath Path of the file to be added, under the root folder
|
115 |
+
* @param string $existingFileMode Controls what to do when trying to add an existing file
|
116 |
+
* @param int $compressionMethod The compression method
|
117 |
+
* @return void
|
118 |
+
*/
|
119 |
+
protected function addFileToArchiveWithCompressionMethod($rootFolderPath, $localFilePath, $existingFileMode, $compressionMethod)
|
120 |
+
{
|
121 |
+
$zip = $this->createOrGetZip();
|
122 |
+
|
123 |
+
if (!$this->shouldSkipFile($zip, $localFilePath, $existingFileMode)) {
|
124 |
+
$normalizedFullFilePath = $this->getNormalizedRealPath($rootFolderPath . '/' . $localFilePath);
|
125 |
+
$zip->addFile($normalizedFullFilePath, $localFilePath);
|
126 |
+
|
127 |
+
if (self::canChooseCompressionMethod()) {
|
128 |
+
$zip->setCompressionName($localFilePath, $compressionMethod);
|
129 |
+
}
|
130 |
+
}
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* @return bool Whether it is possible to choose the desired compression method to be used
|
135 |
+
*/
|
136 |
+
public static function canChooseCompressionMethod()
|
137 |
+
{
|
138 |
+
// setCompressionName() is a PHP7+ method...
|
139 |
+
return (method_exists(new \ZipArchive(), 'setCompressionName'));
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* @param string $folderPath Path to the folder to be zipped
|
144 |
+
* @param string|void $existingFileMode Controls what to do when trying to add an existing file
|
145 |
+
* @return void
|
146 |
+
*/
|
147 |
+
public function addFolderToArchive($folderPath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
|
148 |
+
{
|
149 |
+
$zip = $this->createOrGetZip();
|
150 |
+
|
151 |
+
$folderRealPath = $this->getNormalizedRealPath($folderPath) . '/';
|
152 |
+
$itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
|
153 |
+
|
154 |
+
foreach ($itemIterator as $itemInfo) {
|
155 |
+
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
|
156 |
+
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
|
157 |
+
|
158 |
+
if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
|
159 |
+
$zip->addFile($itemRealPath, $itemLocalPath);
|
160 |
+
}
|
161 |
+
}
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* @param \ZipArchive $zip
|
166 |
+
* @param string $itemLocalPath
|
167 |
+
* @param string $existingFileMode
|
168 |
+
* @return bool Whether the file should be added to the archive or skipped
|
169 |
+
*/
|
170 |
+
protected function shouldSkipFile($zip, $itemLocalPath, $existingFileMode)
|
171 |
+
{
|
172 |
+
// Skip files if:
|
173 |
+
// - EXISTING_FILES_SKIP mode chosen
|
174 |
+
// - File already exists in the archive
|
175 |
+
return ($existingFileMode === self::EXISTING_FILES_SKIP && $zip->locateName($itemLocalPath) !== false);
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Returns canonicalized absolute pathname, containing only forward slashes.
|
180 |
+
*
|
181 |
+
* @param string $path Path to normalize
|
182 |
+
* @return string Normalized and canonicalized path
|
183 |
+
*/
|
184 |
+
protected function getNormalizedRealPath($path)
|
185 |
+
{
|
186 |
+
$realPath = realpath($path);
|
187 |
+
return str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Closes the archive and copies it into the given stream
|
192 |
+
*
|
193 |
+
* @param resource $streamPointer Pointer to the stream to copy the zip
|
194 |
+
* @return void
|
195 |
+
*/
|
196 |
+
public function closeArchiveAndCopyToStream($streamPointer)
|
197 |
+
{
|
198 |
+
$zip = $this->createOrGetZip();
|
199 |
+
$zip->close();
|
200 |
+
unset($this->zip);
|
201 |
+
|
202 |
+
$this->copyZipToStream($streamPointer);
|
203 |
+
}
|
204 |
+
|
205 |
+
/**
|
206 |
+
* Streams the contents of the zip file into the given stream
|
207 |
+
*
|
208 |
+
* @param resource $pointer Pointer to the stream to copy the zip
|
209 |
+
* @return void
|
210 |
+
*/
|
211 |
+
protected function copyZipToStream($pointer)
|
212 |
+
{
|
213 |
+
$zipFilePointer = fopen($this->getZipFilePath(), 'r');
|
214 |
+
stream_copy_to_stream($zipFilePointer, $pointer);
|
215 |
+
fclose($zipFilePointer);
|
216 |
+
}
|
217 |
+
}
|
app/Services/Spout/Writer/Common/Internal/AbstractWorkbook.php
ADDED
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common\Internal;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\SheetNotFoundException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class Workbook
|
9 |
+
* Represents a workbook within a spreadsheet file.
|
10 |
+
* It provides the functions to work with worksheets.
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Writer\Common
|
13 |
+
*/
|
14 |
+
abstract class AbstractWorkbook implements WorkbookInterface
|
15 |
+
{
|
16 |
+
/** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */
|
17 |
+
protected $shouldCreateNewSheetsAutomatically;
|
18 |
+
|
19 |
+
/** @var string Timestamp based unique ID identifying the workbook */
|
20 |
+
protected $internalId;
|
21 |
+
|
22 |
+
/** @var WorksheetInterface[] Array containing the workbook's sheets */
|
23 |
+
protected $worksheets = [];
|
24 |
+
|
25 |
+
/** @var WorksheetInterface The worksheet where data will be written to */
|
26 |
+
protected $currentWorksheet;
|
27 |
+
|
28 |
+
/**
|
29 |
+
* @param bool $shouldCreateNewSheetsAutomatically
|
30 |
+
* @param \Box\Spout\Writer\Style\Style $defaultRowStyle
|
31 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
|
32 |
+
*/
|
33 |
+
public function __construct($shouldCreateNewSheetsAutomatically, $defaultRowStyle)
|
34 |
+
{
|
35 |
+
$this->shouldCreateNewSheetsAutomatically = $shouldCreateNewSheetsAutomatically;
|
36 |
+
$this->internalId = uniqid();
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @return \Box\Spout\Writer\Common\Helper\AbstractStyleHelper The specific style helper
|
41 |
+
*/
|
42 |
+
abstract protected function getStyleHelper();
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @return int Maximum number of rows/columns a sheet can contain
|
46 |
+
*/
|
47 |
+
abstract protected function getMaxRowsPerWorksheet();
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Creates a new sheet in the workbook. The current sheet remains unchanged.
|
51 |
+
*
|
52 |
+
* @return WorksheetInterface The created sheet
|
53 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
|
54 |
+
*/
|
55 |
+
abstract public function addNewSheet();
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Creates a new sheet in the workbook and make it the current sheet.
|
59 |
+
* The writing will resume where it stopped (i.e. data won't be truncated).
|
60 |
+
*
|
61 |
+
* @return WorksheetInterface The created sheet
|
62 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
|
63 |
+
*/
|
64 |
+
public function addNewSheetAndMakeItCurrent()
|
65 |
+
{
|
66 |
+
$worksheet = $this->addNewSheet();
|
67 |
+
$this->setCurrentWorksheet($worksheet);
|
68 |
+
|
69 |
+
return $worksheet;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* @return WorksheetInterface[] All the workbook's sheets
|
74 |
+
*/
|
75 |
+
public function getWorksheets()
|
76 |
+
{
|
77 |
+
return $this->worksheets;
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Returns the current sheet
|
82 |
+
*
|
83 |
+
* @return WorksheetInterface The current sheet
|
84 |
+
*/
|
85 |
+
public function getCurrentWorksheet()
|
86 |
+
{
|
87 |
+
return $this->currentWorksheet;
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Sets the given sheet as the current one. New data will be written to this sheet.
|
92 |
+
* The writing will resume where it stopped (i.e. data won't be truncated).
|
93 |
+
*
|
94 |
+
* @param \Box\Spout\Writer\Common\Sheet $sheet The "external" sheet to set as current
|
95 |
+
* @return void
|
96 |
+
* @throws \Box\Spout\Writer\Exception\SheetNotFoundException If the given sheet does not exist in the workbook
|
97 |
+
*/
|
98 |
+
public function setCurrentSheet($sheet)
|
99 |
+
{
|
100 |
+
$worksheet = $this->getWorksheetFromExternalSheet($sheet);
|
101 |
+
if ($worksheet !== null) {
|
102 |
+
$this->currentWorksheet = $worksheet;
|
103 |
+
} else {
|
104 |
+
throw new SheetNotFoundException('The given sheet does not exist in the workbook.');
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* @param WorksheetInterface $worksheet
|
110 |
+
* @return void
|
111 |
+
*/
|
112 |
+
protected function setCurrentWorksheet($worksheet)
|
113 |
+
{
|
114 |
+
$this->currentWorksheet = $worksheet;
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Returns the worksheet associated to the given external sheet.
|
119 |
+
*
|
120 |
+
* @param \Box\Spout\Writer\Common\Sheet $sheet
|
121 |
+
* @return WorksheetInterface|null The worksheet associated to the given external sheet or null if not found.
|
122 |
+
*/
|
123 |
+
protected function getWorksheetFromExternalSheet($sheet)
|
124 |
+
{
|
125 |
+
$worksheetFound = null;
|
126 |
+
|
127 |
+
foreach ($this->worksheets as $worksheet) {
|
128 |
+
if ($worksheet->getExternalSheet() === $sheet) {
|
129 |
+
$worksheetFound = $worksheet;
|
130 |
+
break;
|
131 |
+
}
|
132 |
+
}
|
133 |
+
|
134 |
+
return $worksheetFound;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Adds data to the current sheet.
|
139 |
+
* If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination
|
140 |
+
* with the creation of new worksheets if one worksheet has reached its maximum capicity.
|
141 |
+
*
|
142 |
+
* @param array $dataRow Array containing data to be written. Cannot be empty.
|
143 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
144 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
|
145 |
+
* @return void
|
146 |
+
* @throws \Box\Spout\Common\Exception\IOException If trying to create a new sheet and unable to open the sheet for writing
|
147 |
+
* @throws \Box\Spout\Writer\Exception\WriterException If unable to write data
|
148 |
+
*/
|
149 |
+
public function addRowToCurrentWorksheet($dataRow, $style)
|
150 |
+
{
|
151 |
+
$currentWorksheet = $this->getCurrentWorksheet();
|
152 |
+
$hasReachedMaxRows = $this->hasCurrentWorkseetReachedMaxRows();
|
153 |
+
$styleHelper = $this->getStyleHelper();
|
154 |
+
|
155 |
+
// if we reached the maximum number of rows for the current sheet...
|
156 |
+
if ($hasReachedMaxRows) {
|
157 |
+
// ... continue writing in a new sheet if option set
|
158 |
+
if ($this->shouldCreateNewSheetsAutomatically) {
|
159 |
+
$currentWorksheet = $this->addNewSheetAndMakeItCurrent();
|
160 |
+
|
161 |
+
$updatedStyle = $styleHelper->applyExtraStylesIfNeeded($style, $dataRow);
|
162 |
+
$registeredStyle = $styleHelper->registerStyle($updatedStyle);
|
163 |
+
$currentWorksheet->addRow($dataRow, $registeredStyle);
|
164 |
+
} else {
|
165 |
+
// otherwise, do nothing as the data won't be read anyways
|
166 |
+
}
|
167 |
+
} else {
|
168 |
+
$updatedStyle = $styleHelper->applyExtraStylesIfNeeded($style, $dataRow);
|
169 |
+
$registeredStyle = $styleHelper->registerStyle($updatedStyle);
|
170 |
+
$currentWorksheet->addRow($dataRow, $registeredStyle);
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* @return bool Whether the current worksheet has reached the maximum number of rows per sheet.
|
176 |
+
*/
|
177 |
+
protected function hasCurrentWorkseetReachedMaxRows()
|
178 |
+
{
|
179 |
+
$currentWorksheet = $this->getCurrentWorksheet();
|
180 |
+
return ($currentWorksheet->getLastWrittenRowIndex() >= $this->getMaxRowsPerWorksheet());
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Closes the workbook and all its associated sheets.
|
185 |
+
* All the necessary files are written to disk and zipped together to create the ODS file.
|
186 |
+
* All the temporary files are then deleted.
|
187 |
+
*
|
188 |
+
* @param resource $finalFilePointer Pointer to the ODS that will be created
|
189 |
+
* @return void
|
190 |
+
*/
|
191 |
+
abstract public function close($finalFilePointer);
|
192 |
+
}
|
app/Services/Spout/Writer/Common/Internal/WorkbookInterface.php
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common\Internal;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface WorkbookInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Writer\Common\Internal
|
9 |
+
*/
|
10 |
+
interface WorkbookInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Creates a new sheet in the workbook. The current sheet remains unchanged.
|
14 |
+
*
|
15 |
+
* @return WorksheetInterface The created sheet
|
16 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
|
17 |
+
*/
|
18 |
+
public function addNewSheet();
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Creates a new sheet in the workbook and make it the current sheet.
|
22 |
+
* The writing will resume where it stopped (i.e. data won't be truncated).
|
23 |
+
*
|
24 |
+
* @return WorksheetInterface The created sheet
|
25 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
|
26 |
+
*/
|
27 |
+
public function addNewSheetAndMakeItCurrent();
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @return WorksheetInterface[] All the workbook's sheets
|
31 |
+
*/
|
32 |
+
public function getWorksheets();
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Returns the current sheet
|
36 |
+
*
|
37 |
+
* @return WorksheetInterface The current sheet
|
38 |
+
*/
|
39 |
+
public function getCurrentWorksheet();
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Sets the given sheet as the current one. New data will be written to this sheet.
|
43 |
+
* The writing will resume where it stopped (i.e. data won't be truncated).
|
44 |
+
*
|
45 |
+
* @param \Box\Spout\Writer\Common\Sheet $sheet The "external" sheet to set as current
|
46 |
+
* @return void
|
47 |
+
* @throws \Box\Spout\Writer\Exception\SheetNotFoundException If the given sheet does not exist in the workbook
|
48 |
+
*/
|
49 |
+
public function setCurrentSheet($sheet);
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Adds data to the current sheet.
|
53 |
+
* If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination
|
54 |
+
* with the creation of new worksheets if one worksheet has reached its maximum capicity.
|
55 |
+
*
|
56 |
+
* @param array $dataRow Array containing data to be written.
|
57 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
58 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
|
59 |
+
* @return void
|
60 |
+
* @throws \Box\Spout\Common\Exception\IOException If trying to create a new sheet and unable to open the sheet for writing
|
61 |
+
* @throws \Box\Spout\Writer\Exception\WriterException If unable to write data
|
62 |
+
*/
|
63 |
+
public function addRowToCurrentWorksheet($dataRow, $style);
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Closes the workbook and all its associated sheets.
|
67 |
+
* All the necessary files are written to disk and zipped together to create the ODS file.
|
68 |
+
* All the temporary files are then deleted.
|
69 |
+
*
|
70 |
+
* @param resource $finalFilePointer Pointer to the ODS that will be created
|
71 |
+
* @return void
|
72 |
+
*/
|
73 |
+
public function close($finalFilePointer);
|
74 |
+
}
|
app/Services/Spout/Writer/Common/Internal/WorksheetInterface.php
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common\Internal;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface WorksheetInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Writer\Common\Internal
|
9 |
+
*/
|
10 |
+
interface WorksheetInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* @return \Box\Spout\Writer\Common\Sheet The "external" sheet
|
14 |
+
*/
|
15 |
+
public function getExternalSheet();
|
16 |
+
|
17 |
+
/**
|
18 |
+
* @return int The index of the last written row
|
19 |
+
*/
|
20 |
+
public function getLastWrittenRowIndex();
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Adds data to the worksheet.
|
24 |
+
*
|
25 |
+
* @param array $dataRow Array containing data to be written.
|
26 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
27 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style.
|
28 |
+
* @return void
|
29 |
+
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
|
30 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
31 |
+
*/
|
32 |
+
public function addRow($dataRow, $style);
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Closes the worksheet
|
36 |
+
*
|
37 |
+
* @return void
|
38 |
+
*/
|
39 |
+
public function close();
|
40 |
+
}
|
app/Services/Spout/Writer/Common/Sheet.php
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Common;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Helper\StringHelper;
|
6 |
+
use Box\Spout\Writer\Exception\InvalidSheetNameException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class Sheet
|
10 |
+
* External representation of a worksheet
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Writer\Common
|
13 |
+
*/
|
14 |
+
class Sheet
|
15 |
+
{
|
16 |
+
const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
|
17 |
+
|
18 |
+
/** Sheet name should not exceed 31 characters */
|
19 |
+
const MAX_LENGTH_SHEET_NAME = 31;
|
20 |
+
|
21 |
+
/** @var array Invalid characters that cannot be contained in the sheet name */
|
22 |
+
private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
|
23 |
+
|
24 |
+
/** @var array Associative array [WORKBOOK_ID] => [[SHEET_INDEX] => [SHEET_NAME]] keeping track of sheets' name to enforce uniqueness per workbook */
|
25 |
+
protected static $SHEETS_NAME_USED = [];
|
26 |
+
|
27 |
+
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
28 |
+
protected $index;
|
29 |
+
|
30 |
+
/** @var string ID of the sheet's associated workbook. Used to restrict sheet name uniqueness enforcement to a single workbook */
|
31 |
+
protected $associatedWorkbookId;
|
32 |
+
|
33 |
+
/** @var string Name of the sheet */
|
34 |
+
protected $name;
|
35 |
+
|
36 |
+
/** @var \Box\Spout\Common\Helper\StringHelper */
|
37 |
+
protected $stringHelper;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
41 |
+
* @param string $associatedWorkbookId ID of the sheet's associated workbook
|
42 |
+
*/
|
43 |
+
public function __construct($sheetIndex, $associatedWorkbookId)
|
44 |
+
{
|
45 |
+
$this->index = $sheetIndex;
|
46 |
+
$this->associatedWorkbookId = $associatedWorkbookId;
|
47 |
+
if (!isset(self::$SHEETS_NAME_USED[$associatedWorkbookId])) {
|
48 |
+
self::$SHEETS_NAME_USED[$associatedWorkbookId] = [];
|
49 |
+
}
|
50 |
+
|
51 |
+
$this->stringHelper = new StringHelper();
|
52 |
+
$this->setName(self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1));
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @api
|
57 |
+
* @return int Index of the sheet, based on order in the workbook (zero-based)
|
58 |
+
*/
|
59 |
+
public function getIndex()
|
60 |
+
{
|
61 |
+
return $this->index;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* @api
|
66 |
+
* @return string Name of the sheet
|
67 |
+
*/
|
68 |
+
public function getName()
|
69 |
+
{
|
70 |
+
return $this->name;
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Sets the name of the sheet. Note that Excel has some restrictions on the name:
|
75 |
+
* - it should not be blank
|
76 |
+
* - it should not exceed 31 characters
|
77 |
+
* - it should not contain these characters: \ / ? * : [ or ]
|
78 |
+
* - it should be unique
|
79 |
+
*
|
80 |
+
* @api
|
81 |
+
* @param string $name Name of the sheet
|
82 |
+
* @return Sheet
|
83 |
+
* @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
|
84 |
+
*/
|
85 |
+
public function setName($name)
|
86 |
+
{
|
87 |
+
$this->throwIfNameIsInvalid($name);
|
88 |
+
|
89 |
+
$this->name = $name;
|
90 |
+
self::$SHEETS_NAME_USED[$this->associatedWorkbookId][$this->index] = $name;
|
91 |
+
|
92 |
+
return $this;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Throws an exception if the given sheet's name is not valid.
|
97 |
+
* @see Sheet::setName for validity rules.
|
98 |
+
*
|
99 |
+
* @param string $name
|
100 |
+
* @return void
|
101 |
+
* @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
|
102 |
+
*/
|
103 |
+
protected function throwIfNameIsInvalid($name)
|
104 |
+
{
|
105 |
+
if (!is_string($name)) {
|
106 |
+
$actualType = gettype($name);
|
107 |
+
$errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
|
108 |
+
throw new InvalidSheetNameException($errorMessage);
|
109 |
+
}
|
110 |
+
|
111 |
+
$failedRequirements = [];
|
112 |
+
$nameLength = $this->stringHelper->getStringLength($name);
|
113 |
+
|
114 |
+
if (!$this->isNameUnique($name)) {
|
115 |
+
$failedRequirements[] = 'It should be unique';
|
116 |
+
} else {
|
117 |
+
if ($nameLength === 0) {
|
118 |
+
$failedRequirements[] = 'It should not be blank';
|
119 |
+
} else {
|
120 |
+
if ($nameLength > self::MAX_LENGTH_SHEET_NAME) {
|
121 |
+
$failedRequirements[] = 'It should not exceed 31 characters';
|
122 |
+
}
|
123 |
+
|
124 |
+
if ($this->doesContainInvalidCharacters($name)) {
|
125 |
+
$failedRequirements[] = 'It should not contain these characters: \\ / ? * : [ or ]';
|
126 |
+
}
|
127 |
+
|
128 |
+
if ($this->doesStartOrEndWithSingleQuote($name)) {
|
129 |
+
$failedRequirements[] = 'It should not start or end with a single quote';
|
130 |
+
}
|
131 |
+
}
|
132 |
+
}
|
133 |
+
|
134 |
+
if (count($failedRequirements) !== 0) {
|
135 |
+
$errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
|
136 |
+
$errorMessage .= implode("\n - ", $failedRequirements);
|
137 |
+
throw new InvalidSheetNameException($errorMessage);
|
138 |
+
}
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Returns whether the given name contains at least one invalid character.
|
143 |
+
* @see Sheet::$INVALID_CHARACTERS_IN_SHEET_NAME for the full list.
|
144 |
+
*
|
145 |
+
* @param string $name
|
146 |
+
* @return bool TRUE if the name contains invalid characters, FALSE otherwise.
|
147 |
+
*/
|
148 |
+
protected function doesContainInvalidCharacters($name)
|
149 |
+
{
|
150 |
+
return (str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
|
151 |
+
}
|
152 |
+
|
153 |
+
/**
|
154 |
+
* Returns whether the given name starts or ends with a single quote
|
155 |
+
*
|
156 |
+
* @param string $name
|
157 |
+
* @return bool TRUE if the name starts or ends with a single quote, FALSE otherwise.
|
158 |
+
*/
|
159 |
+
protected function doesStartOrEndWithSingleQuote($name)
|
160 |
+
{
|
161 |
+
$startsWithSingleQuote = ($this->stringHelper->getCharFirstOccurrencePosition('\'', $name) === 0);
|
162 |
+
$endsWithSingleQuote = ($this->stringHelper->getCharLastOccurrencePosition('\'', $name) === ($this->stringHelper->getStringLength($name) - 1));
|
163 |
+
|
164 |
+
return ($startsWithSingleQuote || $endsWithSingleQuote);
|
165 |
+
}
|
166 |
+
|
167 |
+
/**
|
168 |
+
* Returns whether the given name is unique.
|
169 |
+
*
|
170 |
+
* @param string $name
|
171 |
+
* @return bool TRUE if the name is unique, FALSE otherwise.
|
172 |
+
*/
|
173 |
+
protected function isNameUnique($name)
|
174 |
+
{
|
175 |
+
foreach (self::$SHEETS_NAME_USED[$this->associatedWorkbookId] as $sheetIndex => $sheetName) {
|
176 |
+
if ($sheetIndex !== $this->index && $sheetName === $name) {
|
177 |
+
return false;
|
178 |
+
}
|
179 |
+
}
|
180 |
+
|
181 |
+
return true;
|
182 |
+
}
|
183 |
+
}
|
app/Services/Spout/Writer/Exception/Border/InvalidNameException.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception\Border;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\WriterException;
|
6 |
+
use Box\Spout\Writer\Style\BorderPart;
|
7 |
+
|
8 |
+
class InvalidNameException extends WriterException
|
9 |
+
{
|
10 |
+
public function __construct($name)
|
11 |
+
{
|
12 |
+
$msg = '%s is not a valid name identifier for a border. Valid identifiers are: %s.';
|
13 |
+
|
14 |
+
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedNames())));
|
15 |
+
}
|
16 |
+
}
|
app/Services/Spout/Writer/Exception/Border/InvalidStyleException.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception\Border;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\WriterException;
|
6 |
+
use Box\Spout\Writer\Style\BorderPart;
|
7 |
+
|
8 |
+
class InvalidStyleException extends WriterException
|
9 |
+
{
|
10 |
+
public function __construct($name)
|
11 |
+
{
|
12 |
+
$msg = '%s is not a valid style identifier for a border. Valid identifiers are: %s.';
|
13 |
+
|
14 |
+
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedStyles())));
|
15 |
+
}
|
16 |
+
}
|
app/Services/Spout/Writer/Exception/Border/InvalidWidthException.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception\Border;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\WriterException;
|
6 |
+
use Box\Spout\Writer\Style\BorderPart;
|
7 |
+
|
8 |
+
class InvalidWidthException extends WriterException
|
9 |
+
{
|
10 |
+
public function __construct($name)
|
11 |
+
{
|
12 |
+
$msg = '%s is not a valid width identifier for a border. Valid identifiers are: %s.';
|
13 |
+
|
14 |
+
parent::__construct(sprintf($msg, $name, implode(',', BorderPart::getAllowedWidths())));
|
15 |
+
}
|
16 |
+
}
|
app/Services/Spout/Writer/Exception/InvalidColorException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class InvalidColorException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Writer\Exception
|
10 |
+
*/
|
11 |
+
class InvalidColorException extends WriterException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Writer/Exception/InvalidSheetNameException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class InvalidSheetNameException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Writer\Exception
|
10 |
+
*/
|
11 |
+
class InvalidSheetNameException extends WriterException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Writer/Exception/SheetNotFoundException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class SheetNotFoundException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Writer\Exception
|
10 |
+
*/
|
11 |
+
class SheetNotFoundException extends WriterException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Writer/Exception/WriterAlreadyOpenedException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class WriterAlreadyOpenedException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Writer\Exception
|
10 |
+
*/
|
11 |
+
class WriterAlreadyOpenedException extends WriterException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Writer/Exception/WriterException.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\SpoutException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class WriterException
|
9 |
+
*
|
10 |
+
* @package Box\Spout\Writer\Exception
|
11 |
+
* @abstract
|
12 |
+
*/
|
13 |
+
abstract class WriterException extends SpoutException
|
14 |
+
{
|
15 |
+
}
|
app/Services/Spout/Writer/Exception/WriterNotOpenedException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class WriterNotOpenedException
|
7 |
+
*
|
8 |
+
* @api
|
9 |
+
* @package Box\Spout\Writer\Exception
|
10 |
+
*/
|
11 |
+
class WriterNotOpenedException extends WriterException
|
12 |
+
{
|
13 |
+
}
|
app/Services/Spout/Writer/ODS/Helper/BorderHelper.php
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\ODS\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Style\BorderPart;
|
6 |
+
use Box\Spout\Writer\Style\Border;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class BorderHelper
|
10 |
+
*
|
11 |
+
* The fo:border, fo:border-top, fo:border-bottom, fo:border-left and fo:border-right attributes
|
12 |
+
* specify border properties
|
13 |
+
* http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1419780_253892949
|
14 |
+
*
|
15 |
+
* Example table-cell-properties
|
16 |
+
*
|
17 |
+
* <style:table-cell-properties
|
18 |
+
* fo:border-bottom="0.74pt solid #ffc000" style:diagonal-bl-tr="none"
|
19 |
+
* style:diagonal-tl-br="none" fo:border-left="none" fo:border-right="none"
|
20 |
+
* style:rotation-align="none" fo:border-top="none"/>
|
21 |
+
*/
|
22 |
+
class BorderHelper
|
23 |
+
{
|
24 |
+
/**
|
25 |
+
* Width mappings
|
26 |
+
*
|
27 |
+
* @var array
|
28 |
+
*/
|
29 |
+
protected static $widthMap = [
|
30 |
+
Border::WIDTH_THIN => '0.75pt',
|
31 |
+
Border::WIDTH_MEDIUM => '1.75pt',
|
32 |
+
Border::WIDTH_THICK => '2.5pt',
|
33 |
+
];
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Style mapping
|
37 |
+
*
|
38 |
+
* @var array
|
39 |
+
*/
|
40 |
+
protected static $styleMap = [
|
41 |
+
Border::STYLE_SOLID => 'solid',
|
42 |
+
Border::STYLE_DASHED => 'dashed',
|
43 |
+
Border::STYLE_DOTTED => 'dotted',
|
44 |
+
Border::STYLE_DOUBLE => 'double',
|
45 |
+
];
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @param BorderPart $borderPart
|
49 |
+
* @return string
|
50 |
+
*/
|
51 |
+
public static function serializeBorderPart(BorderPart $borderPart)
|
52 |
+
{
|
53 |
+
$definition = 'fo:border-%s="%s"';
|
54 |
+
|
55 |
+
if ($borderPart->getStyle() === Border::STYLE_NONE) {
|
56 |
+
$borderPartDefinition = sprintf($definition, $borderPart->getName(), 'none');
|
57 |
+
} else {
|
58 |
+
$attributes = [
|
59 |
+
self::$widthMap[$borderPart->getWidth()],
|
60 |
+
self::$styleMap[$borderPart->getStyle()],
|
61 |
+
'#' . $borderPart->getColor(),
|
62 |
+
];
|
63 |
+
$borderPartDefinition = sprintf($definition, $borderPart->getName(), implode(' ', $attributes));
|
64 |
+
}
|
65 |
+
|
66 |
+
return $borderPartDefinition;
|
67 |
+
}
|
68 |
+
}
|
app/Services/Spout/Writer/ODS/Helper/FileSystemHelper.php
ADDED
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\ODS\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Common\Helper\ZipHelper;
|
6 |
+
use Box\Spout\Writer\ODS\Internal\Worksheet;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class FileSystemHelper
|
10 |
+
* This class provides helper functions to help with the file system operations
|
11 |
+
* like files/folders creation & deletion for ODS files
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Writer\ODS\Helper
|
14 |
+
*/
|
15 |
+
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper
|
16 |
+
{
|
17 |
+
const APP_NAME = 'Spout';
|
18 |
+
const MIMETYPE = 'application/vnd.oasis.opendocument.spreadsheet';
|
19 |
+
|
20 |
+
const META_INF_FOLDER_NAME = 'META-INF';
|
21 |
+
const SHEETS_CONTENT_TEMP_FOLDER_NAME = 'worksheets-temp';
|
22 |
+
|
23 |
+
const MANIFEST_XML_FILE_NAME = 'manifest.xml';
|
24 |
+
const CONTENT_XML_FILE_NAME = 'content.xml';
|
25 |
+
const META_XML_FILE_NAME = 'meta.xml';
|
26 |
+
const MIMETYPE_FILE_NAME = 'mimetype';
|
27 |
+
const STYLES_XML_FILE_NAME = 'styles.xml';
|
28 |
+
|
29 |
+
/** @var string Path to the root folder inside the temp folder where the files to create the ODS will be stored */
|
30 |
+
protected $rootFolder;
|
31 |
+
|
32 |
+
/** @var string Path to the "META-INF" folder inside the root folder */
|
33 |
+
protected $metaInfFolder;
|
34 |
+
|
35 |
+
/** @var string Path to the temp folder, inside the root folder, where specific sheets content will be written to */
|
36 |
+
protected $sheetsContentTempFolder;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @return string
|
40 |
+
*/
|
41 |
+
public function getRootFolder()
|
42 |
+
{
|
43 |
+
return $this->rootFolder;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @return string
|
48 |
+
*/
|
49 |
+
public function getSheetsContentTempFolder()
|
50 |
+
{
|
51 |
+
return $this->sheetsContentTempFolder;
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Creates all the folders needed to create a ODS file, as well as the files that won't change.
|
56 |
+
*
|
57 |
+
* @return void
|
58 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
|
59 |
+
*/
|
60 |
+
public function createBaseFilesAndFolders()
|
61 |
+
{
|
62 |
+
$this
|
63 |
+
->createRootFolder()
|
64 |
+
->createMetaInfoFolderAndFile()
|
65 |
+
->createSheetsContentTempFolder()
|
66 |
+
->createMetaFile()
|
67 |
+
->createMimetypeFile();
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Creates the folder that will be used as root
|
72 |
+
*
|
73 |
+
* @return FileSystemHelper
|
74 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
|
75 |
+
*/
|
76 |
+
protected function createRootFolder()
|
77 |
+
{
|
78 |
+
$this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('ods'));
|
79 |
+
return $this;
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Creates the "META-INF" folder under the root folder as well as the "manifest.xml" file in it
|
84 |
+
*
|
85 |
+
* @return FileSystemHelper
|
86 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or the "manifest.xml" file
|
87 |
+
*/
|
88 |
+
protected function createMetaInfoFolderAndFile()
|
89 |
+
{
|
90 |
+
$this->metaInfFolder = $this->createFolder($this->rootFolder, self::META_INF_FOLDER_NAME);
|
91 |
+
|
92 |
+
$this->createManifestFile();
|
93 |
+
|
94 |
+
return $this;
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Creates the "manifest.xml" file under the "META-INF" folder (under root)
|
99 |
+
*
|
100 |
+
* @return FileSystemHelper
|
101 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file
|
102 |
+
*/
|
103 |
+
protected function createManifestFile()
|
104 |
+
{
|
105 |
+
$manifestXmlFileContents = <<<EOD
|
106 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
107 |
+
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">
|
108 |
+
<manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
|
109 |
+
<manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
|
110 |
+
<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
|
111 |
+
<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
|
112 |
+
</manifest:manifest>
|
113 |
+
EOD;
|
114 |
+
|
115 |
+
$this->createFileWithContents($this->metaInfFolder, self::MANIFEST_XML_FILE_NAME, $manifestXmlFileContents);
|
116 |
+
|
117 |
+
return $this;
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Creates the temp folder where specific sheets content will be written to.
|
122 |
+
* This folder is not part of the final ODS file and is only used to be able to jump between sheets.
|
123 |
+
*
|
124 |
+
* @return FileSystemHelper
|
125 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
|
126 |
+
*/
|
127 |
+
protected function createSheetsContentTempFolder()
|
128 |
+
{
|
129 |
+
$this->sheetsContentTempFolder = $this->createFolder($this->rootFolder, self::SHEETS_CONTENT_TEMP_FOLDER_NAME);
|
130 |
+
return $this;
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Creates the "meta.xml" file under the root folder
|
135 |
+
*
|
136 |
+
* @return FileSystemHelper
|
137 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file
|
138 |
+
*/
|
139 |
+
protected function createMetaFile()
|
140 |
+
{
|
141 |
+
$appName = self::APP_NAME;
|
142 |
+
$createdDate = (new \DateTime())->format(\DateTime::W3C);
|
143 |
+
|
144 |
+
$metaXmlFileContents = <<<EOD
|
145 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
146 |
+
<office:document-meta office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
147 |
+
<office:meta>
|
148 |
+
<dc:creator>$appName</dc:creator>
|
149 |
+
<meta:creation-date>$createdDate</meta:creation-date>
|
150 |
+
<dc:date>$createdDate</dc:date>
|
151 |
+
</office:meta>
|
152 |
+
</office:document-meta>
|
153 |
+
EOD;
|
154 |
+
|
155 |
+
$this->createFileWithContents($this->rootFolder, self::META_XML_FILE_NAME, $metaXmlFileContents);
|
156 |
+
|
157 |
+
return $this;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Creates the "mimetype" file under the root folder
|
162 |
+
*
|
163 |
+
* @return FileSystemHelper
|
164 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file
|
165 |
+
*/
|
166 |
+
protected function createMimetypeFile()
|
167 |
+
{
|
168 |
+
$this->createFileWithContents($this->rootFolder, self::MIMETYPE_FILE_NAME, self::MIMETYPE);
|
169 |
+
return $this;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Creates the "content.xml" file under the root folder
|
174 |
+
*
|
175 |
+
* @param Worksheet[] $worksheets
|
176 |
+
* @param StyleHelper $styleHelper
|
177 |
+
* @return FileSystemHelper
|
178 |
+
*/
|
179 |
+
public function createContentFile($worksheets, $styleHelper)
|
180 |
+
{
|
181 |
+
$contentXmlFileContents = <<<EOD
|
182 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
183 |
+
<office:document-content office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
184 |
+
EOD;
|
185 |
+
|
186 |
+
$contentXmlFileContents .= $styleHelper->getContentXmlFontFaceSectionContent();
|
187 |
+
$contentXmlFileContents .= $styleHelper->getContentXmlAutomaticStylesSectionContent(count($worksheets));
|
188 |
+
|
189 |
+
$contentXmlFileContents .= '<office:body><office:spreadsheet>';
|
190 |
+
|
191 |
+
$this->createFileWithContents($this->rootFolder, self::CONTENT_XML_FILE_NAME, $contentXmlFileContents);
|
192 |
+
|
193 |
+
// Append sheets content to "content.xml"
|
194 |
+
$contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME;
|
195 |
+
$contentXmlHandle = fopen($contentXmlFilePath, 'a');
|
196 |
+
|
197 |
+
foreach ($worksheets as $worksheet) {
|
198 |
+
// write the "<table:table>" node, with the final sheet's name
|
199 |
+
fwrite($contentXmlHandle, $worksheet->getTableElementStartAsString());
|
200 |
+
|
201 |
+
$worksheetFilePath = $worksheet->getWorksheetFilePath();
|
202 |
+
$this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
|
203 |
+
|
204 |
+
fwrite($contentXmlHandle, '</table:table>');
|
205 |
+
}
|
206 |
+
|
207 |
+
$contentXmlFileContents = '</office:spreadsheet></office:body></office:document-content>';
|
208 |
+
|
209 |
+
fwrite($contentXmlHandle, $contentXmlFileContents);
|
210 |
+
fclose($contentXmlHandle);
|
211 |
+
|
212 |
+
return $this;
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Streams the content of the file at the given path into the target resource.
|
217 |
+
* Depending on which mode the target resource was created with, it will truncate then copy
|
218 |
+
* or append the content to the target file.
|
219 |
+
*
|
220 |
+
* @param string $sourceFilePath Path of the file whose content will be copied
|
221 |
+
* @param resource $targetResource Target resource that will receive the content
|
222 |
+
* @return void
|
223 |
+
*/
|
224 |
+
protected function copyFileContentsToTarget($sourceFilePath, $targetResource)
|
225 |
+
{
|
226 |
+
$sourceHandle = fopen($sourceFilePath, 'r');
|
227 |
+
stream_copy_to_stream($sourceHandle, $targetResource);
|
228 |
+
fclose($sourceHandle);
|
229 |
+
}
|
230 |
+
|
231 |
+
/**
|
232 |
+
* Deletes the temporary folder where sheets content was stored.
|
233 |
+
*
|
234 |
+
* @return FileSystemHelper
|
235 |
+
*/
|
236 |
+
public function deleteWorksheetTempFolder()
|
237 |
+
{
|
238 |
+
$this->deleteFolderRecursively($this->sheetsContentTempFolder);
|
239 |
+
return $this;
|
240 |
+
}
|
241 |
+
|
242 |
+
|
243 |
+
/**
|
244 |
+
* Creates the "styles.xml" file under the root folder
|
245 |
+
*
|
246 |
+
* @param StyleHelper $styleHelper
|
247 |
+
* @param int $numWorksheets Number of created worksheets
|
248 |
+
* @return FileSystemHelper
|
249 |
+
*/
|
250 |
+
public function createStylesFile($styleHelper, $numWorksheets)
|
251 |
+
{
|
252 |
+
$stylesXmlFileContents = $styleHelper->getStylesXMLFileContent($numWorksheets);
|
253 |
+
$this->createFileWithContents($this->rootFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents);
|
254 |
+
|
255 |
+
return $this;
|
256 |
+
}
|
257 |
+
|
258 |
+
/**
|
259 |
+
* Zips the root folder and streams the contents of the zip into the given stream
|
260 |
+
*
|
261 |
+
* @param resource $streamPointer Pointer to the stream to copy the zip
|
262 |
+
* @return void
|
263 |
+
*/
|
264 |
+
public function zipRootFolderAndCopyToStream($streamPointer)
|
265 |
+
{
|
266 |
+
$zipHelper = new ZipHelper($this->rootFolder);
|
267 |
+
|
268 |
+
// In order to have the file's mime type detected properly, files need to be added
|
269 |
+
// to the zip file in a particular order.
|
270 |
+
// @see http://www.jejik.com/articles/2010/03/how_to_correctly_create_odf_documents_using_zip/
|
271 |
+
$zipHelper->addUncompressedFileToArchive($this->rootFolder, self::MIMETYPE_FILE_NAME);
|
272 |
+
|
273 |
+
$zipHelper->addFolderToArchive($this->rootFolder, ZipHelper::EXISTING_FILES_SKIP);
|
274 |
+
$zipHelper->closeArchiveAndCopyToStream($streamPointer);
|
275 |
+
|
276 |
+
// once the zip is copied, remove it
|
277 |
+
$this->deleteFile($zipHelper->getZipFilePath());
|
278 |
+
}
|
279 |
+
}
|
app/Services/Spout/Writer/ODS/Helper/StyleHelper.php
ADDED
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\ODS\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Common\Helper\AbstractStyleHelper;
|
6 |
+
use Box\Spout\Writer\Style\BorderPart;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class StyleHelper
|
10 |
+
* This class provides helper functions to manage styles
|
11 |
+
*
|
12 |
+
* @package Box\Spout\Writer\ODS\Helper
|
13 |
+
*/
|
14 |
+
class StyleHelper extends AbstractStyleHelper
|
15 |
+
{
|
16 |
+
/** @var string[] [FONT_NAME] => [] Map whose keys contain all the fonts used */
|
17 |
+
protected $usedFontsSet = [];
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Registers the given style as a used style.
|
21 |
+
* Duplicate styles won't be registered more than once.
|
22 |
+
*
|
23 |
+
* @param \Box\Spout\Writer\Style\Style $style The style to be registered
|
24 |
+
* @return \Box\Spout\Writer\Style\Style The registered style, updated with an internal ID.
|
25 |
+
*/
|
26 |
+
public function registerStyle($style)
|
27 |
+
{
|
28 |
+
$this->usedFontsSet[$style->getFontName()] = true;
|
29 |
+
return parent::registerStyle($style);
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @return string[] List of used fonts name
|
34 |
+
*/
|
35 |
+
protected function getUsedFonts()
|
36 |
+
{
|
37 |
+
return array_keys($this->usedFontsSet);
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Returns the content of the "styles.xml" file, given a list of styles.
|
42 |
+
*
|
43 |
+
* @param int $numWorksheets Number of worksheets created
|
44 |
+
* @return string
|
45 |
+
*/
|
46 |
+
public function getStylesXMLFileContent($numWorksheets)
|
47 |
+
{
|
48 |
+
$content = <<<EOD
|
49 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
50 |
+
<office:document-styles office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
51 |
+
EOD;
|
52 |
+
|
53 |
+
$content .= $this->getFontFaceSectionContent();
|
54 |
+
$content .= $this->getStylesSectionContent();
|
55 |
+
$content .= $this->getAutomaticStylesSectionContent($numWorksheets);
|
56 |
+
$content .= $this->getMasterStylesSectionContent($numWorksheets);
|
57 |
+
|
58 |
+
$content .= <<<EOD
|
59 |
+
</office:document-styles>
|
60 |
+
EOD;
|
61 |
+
|
62 |
+
return $content;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Returns the content of the "<office:font-face-decls>" section, inside "styles.xml" file.
|
67 |
+
*
|
68 |
+
* @return string
|
69 |
+
*/
|
70 |
+
protected function getFontFaceSectionContent()
|
71 |
+
{
|
72 |
+
$content = '<office:font-face-decls>';
|
73 |
+
foreach ($this->getUsedFonts() as $fontName) {
|
74 |
+
$content .= '<style:font-face style:name="' . $fontName . '" svg:font-family="' . $fontName . '"/>';
|
75 |
+
}
|
76 |
+
$content .= '</office:font-face-decls>';
|
77 |
+
|
78 |
+
return $content;
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Returns the content of the "<office:styles>" section, inside "styles.xml" file.
|
83 |
+
*
|
84 |
+
* @return string
|
85 |
+
*/
|
86 |
+
protected function getStylesSectionContent()
|
87 |
+
{
|
88 |
+
$defaultStyle = $this->getDefaultStyle();
|
89 |
+
|
90 |
+
return <<<EOD
|
91 |
+
<office:styles>
|
92 |
+
<number:number-style style:name="N0">
|
93 |
+
<number:number number:min-integer-digits="1"/>
|
94 |
+
</number:number-style>
|
95 |
+
<style:style style:data-style-name="N0" style:family="table-cell" style:name="Default">
|
96 |
+
<style:table-cell-properties fo:background-color="transparent" style:vertical-align="automatic"/>
|
97 |
+
<style:text-properties fo:color="#{$defaultStyle->getFontColor()}"
|
98 |
+
fo:font-size="{$defaultStyle->getFontSize()}pt" style:font-size-asian="{$defaultStyle->getFontSize()}pt" style:font-size-complex="{$defaultStyle->getFontSize()}pt"
|
99 |
+
style:font-name="{$defaultStyle->getFontName()}" style:font-name-asian="{$defaultStyle->getFontName()}" style:font-name-complex="{$defaultStyle->getFontName()}"/>
|
100 |
+
</style:style>
|
101 |
+
</office:styles>
|
102 |
+
EOD;
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Returns the content of the "<office:automatic-styles>" section, inside "styles.xml" file.
|
107 |
+
*
|
108 |
+
* @param int $numWorksheets Number of worksheets created
|
109 |
+
* @return string
|
110 |
+
*/
|
111 |
+
protected function getAutomaticStylesSectionContent($numWorksheets)
|
112 |
+
{
|
113 |
+
$content = '<office:automatic-styles>';
|
114 |
+
|
115 |
+
for ($i = 1; $i <= $numWorksheets; $i++) {
|
116 |
+
$content .= <<<EOD
|
117 |
+
<style:page-layout style:name="pm$i">
|
118 |
+
<style:page-layout-properties style:first-page-number="continue" style:print="objects charts drawings" style:table-centering="none"/>
|
119 |
+
<style:header-style/>
|
120 |
+
<style:footer-style/>
|
121 |
+
</style:page-layout>
|
122 |
+
EOD;
|
123 |
+
}
|
124 |
+
|
125 |
+
$content .= '</office:automatic-styles>';
|
126 |
+
|
127 |
+
return $content;
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Returns the content of the "<office:master-styles>" section, inside "styles.xml" file.
|
132 |
+
*
|
133 |
+
* @param int $numWorksheets Number of worksheets created
|
134 |
+
* @return string
|
135 |
+
*/
|
136 |
+
protected function getMasterStylesSectionContent($numWorksheets)
|
137 |
+
{
|
138 |
+
$content = '<office:master-styles>';
|
139 |
+
|
140 |
+
for ($i = 1; $i <= $numWorksheets; $i++) {
|
141 |
+
$content .= <<<EOD
|
142 |
+
<style:master-page style:name="mp$i" style:page-layout-name="pm$i">
|
143 |
+
<style:header/>
|
144 |
+
<style:header-left style:display="false"/>
|
145 |
+
<style:footer/>
|
146 |
+
<style:footer-left style:display="false"/>
|
147 |
+
</style:master-page>
|
148 |
+
EOD;
|
149 |
+
}
|
150 |
+
|
151 |
+
$content .= '</office:master-styles>';
|
152 |
+
|
153 |
+
return $content;
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Returns the contents of the "<office:font-face-decls>" section, inside "content.xml" file.
|
159 |
+
*
|
160 |
+
* @return string
|
161 |
+
*/
|
162 |
+
public function getContentXmlFontFaceSectionContent()
|
163 |
+
{
|
164 |
+
$content = '<office:font-face-decls>';
|
165 |
+
foreach ($this->getUsedFonts() as $fontName) {
|
166 |
+
$content .= '<style:font-face style:name="' . $fontName . '" svg:font-family="' . $fontName . '"/>';
|
167 |
+
}
|
168 |
+
$content .= '</office:font-face-decls>';
|
169 |
+
|
170 |
+
return $content;
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Returns the contents of the "<office:automatic-styles>" section, inside "content.xml" file.
|
175 |
+
*
|
176 |
+
* @param int $numWorksheets Number of worksheets created
|
177 |
+
* @return string
|
178 |
+
*/
|
179 |
+
public function getContentXmlAutomaticStylesSectionContent($numWorksheets)
|
180 |
+
{
|
181 |
+
$content = '<office:automatic-styles>';
|
182 |
+
|
183 |
+
foreach ($this->getRegisteredStyles() as $style) {
|
184 |
+
$content .= $this->getStyleSectionContent($style);
|
185 |
+
}
|
186 |
+
|
187 |
+
$content .= <<<EOD
|
188 |
+
<style:style style:family="table-column" style:name="co1">
|
189 |
+
<style:table-column-properties fo:break-before="auto"/>
|
190 |
+
</style:style>
|
191 |
+
<style:style style:family="table-row" style:name="ro1">
|
192 |
+
<style:table-row-properties fo:break-before="auto" style:row-height="15pt" style:use-optimal-row-height="true"/>
|
193 |
+
</style:style>
|
194 |
+
EOD;
|
195 |
+
|
196 |
+
for ($i = 1; $i <= $numWorksheets; $i++) {
|
197 |
+
$content .= <<<EOD
|
198 |
+
<style:style style:family="table" style:master-page-name="mp$i" style:name="ta$i">
|
199 |
+
<style:table-properties style:writing-mode="lr-tb" table:display="true"/>
|
200 |
+
</style:style>
|
201 |
+
EOD;
|
202 |
+
}
|
203 |
+
|
204 |
+
$content .= '</office:automatic-styles>';
|
205 |
+
|
206 |
+
return $content;
|
207 |
+
}
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Returns the contents of the "<style:style>" section, inside "<office:automatic-styles>" section
|
211 |
+
*
|
212 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
213 |
+
* @return string
|
214 |
+
*/
|
215 |
+
protected function getStyleSectionContent($style)
|
216 |
+
{
|
217 |
+
$styleIndex = $style->getId() + 1; // 1-based
|
218 |
+
|
219 |
+
$content = '<style:style style:data-style-name="N0" style:family="table-cell" style:name="ce' . $styleIndex . '" style:parent-style-name="Default">';
|
220 |
+
|
221 |
+
$content .= $this->getTextPropertiesSectionContent($style);
|
222 |
+
$content .= $this->getTableCellPropertiesSectionContent($style);
|
223 |
+
|
224 |
+
$content .= '</style:style>';
|
225 |
+
|
226 |
+
return $content;
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Returns the contents of the "<style:text-properties>" section, inside "<style:style>" section
|
231 |
+
*
|
232 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
233 |
+
* @return string
|
234 |
+
*/
|
235 |
+
private function getTextPropertiesSectionContent($style)
|
236 |
+
{
|
237 |
+
$content = '';
|
238 |
+
|
239 |
+
if ($style->shouldApplyFont()) {
|
240 |
+
$content .= $this->getFontSectionContent($style);
|
241 |
+
}
|
242 |
+
|
243 |
+
return $content;
|
244 |
+
}
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Returns the contents of the "<style:text-properties>" section, inside "<style:style>" section
|
248 |
+
*
|
249 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
250 |
+
* @return string
|
251 |
+
*/
|
252 |
+
private function getFontSectionContent($style)
|
253 |
+
{
|
254 |
+
$defaultStyle = $this->getDefaultStyle();
|
255 |
+
|
256 |
+
$content = '<style:text-properties';
|
257 |
+
|
258 |
+
$fontColor = $style->getFontColor();
|
259 |
+
if ($fontColor !== $defaultStyle->getFontColor()) {
|
260 |
+
$content .= ' fo:color="#' . $fontColor . '"';
|
261 |
+
}
|
262 |
+
|
263 |
+
$fontName = $style->getFontName();
|
264 |
+
if ($fontName !== $defaultStyle->getFontName()) {
|
265 |
+
$content .= ' style:font-name="' . $fontName . '" style:font-name-asian="' . $fontName . '" style:font-name-complex="' . $fontName . '"';
|
266 |
+
}
|
267 |
+
|
268 |
+
$fontSize = $style->getFontSize();
|
269 |
+
if ($fontSize !== $defaultStyle->getFontSize()) {
|
270 |
+
$content .= ' fo:font-size="' . $fontSize . 'pt" style:font-size-asian="' . $fontSize . 'pt" style:font-size-complex="' . $fontSize . 'pt"';
|
271 |
+
}
|
272 |
+
|
273 |
+
if ($style->isFontBold()) {
|
274 |
+
$content .= ' fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"';
|
275 |
+
}
|
276 |
+
if ($style->isFontItalic()) {
|
277 |
+
$content .= ' fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"';
|
278 |
+
}
|
279 |
+
if ($style->isFontUnderline()) {
|
280 |
+
$content .= ' style:text-underline-style="solid" style:text-underline-type="single"';
|
281 |
+
}
|
282 |
+
if ($style->isFontStrikethrough()) {
|
283 |
+
$content .= ' style:text-line-through-style="solid"';
|
284 |
+
}
|
285 |
+
|
286 |
+
$content .= '/>';
|
287 |
+
|
288 |
+
return $content;
|
289 |
+
}
|
290 |
+
|
291 |
+
/**
|
292 |
+
* Returns the contents of the "<style:table-cell-properties>" section, inside "<style:style>" section
|
293 |
+
*
|
294 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
295 |
+
* @return string
|
296 |
+
*/
|
297 |
+
private function getTableCellPropertiesSectionContent($style)
|
298 |
+
{
|
299 |
+
$content = '';
|
300 |
+
|
301 |
+
if ($style->shouldWrapText()) {
|
302 |
+
$content .= $this->getWrapTextXMLContent();
|
303 |
+
}
|
304 |
+
|
305 |
+
if ($style->shouldApplyBorder()) {
|
306 |
+
$content .= $this->getBorderXMLContent($style);
|
307 |
+
}
|
308 |
+
|
309 |
+
if ($style->shouldApplyBackgroundColor()) {
|
310 |
+
$content .= $this->getBackgroundColorXMLContent($style);
|
311 |
+
}
|
312 |
+
|
313 |
+
return $content;
|
314 |
+
}
|
315 |
+
|
316 |
+
/**
|
317 |
+
* Returns the contents of the wrap text definition for the "<style:table-cell-properties>" section
|
318 |
+
*
|
319 |
+
* @return string
|
320 |
+
*/
|
321 |
+
private function getWrapTextXMLContent()
|
322 |
+
{
|
323 |
+
return '<style:table-cell-properties fo:wrap-option="wrap" style:vertical-align="automatic"/>';
|
324 |
+
}
|
325 |
+
|
326 |
+
/**
|
327 |
+
* Returns the contents of the borders definition for the "<style:table-cell-properties>" section
|
328 |
+
*
|
329 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
330 |
+
* @return string
|
331 |
+
*/
|
332 |
+
private function getBorderXMLContent($style)
|
333 |
+
{
|
334 |
+
$borderProperty = '<style:table-cell-properties %s />';
|
335 |
+
|
336 |
+
$borders = array_map(function (BorderPart $borderPart) {
|
337 |
+
return BorderHelper::serializeBorderPart($borderPart);
|
338 |
+
}, $style->getBorder()->getParts());
|
339 |
+
|
340 |
+
return sprintf($borderProperty, implode(' ', $borders));
|
341 |
+
}
|
342 |
+
|
343 |
+
/**
|
344 |
+
* Returns the contents of the background color definition for the "<style:table-cell-properties>" section
|
345 |
+
*
|
346 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
347 |
+
* @return string
|
348 |
+
*/
|
349 |
+
private function getBackgroundColorXMLContent($style)
|
350 |
+
{
|
351 |
+
return sprintf(
|
352 |
+
'<style:table-cell-properties fo:background-color="#%s"/>',
|
353 |
+
$style->getBackgroundColor()
|
354 |
+
);
|
355 |
+
}
|
356 |
+
}
|
app/Services/Spout/Writer/ODS/Internal/Workbook.php
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\ODS\Internal;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Common\Internal\AbstractWorkbook;
|
6 |
+
use Box\Spout\Writer\ODS\Helper\FileSystemHelper;
|
7 |
+
use Box\Spout\Writer\ODS\Helper\StyleHelper;
|
8 |
+
use Box\Spout\Writer\Common\Sheet;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Class Workbook
|
12 |
+
* Represents a workbook within a ODS file.
|
13 |
+
* It provides the functions to work with worksheets.
|
14 |
+
*
|
15 |
+
* @package Box\Spout\Writer\ODS\Internal
|
16 |
+
*/
|
17 |
+
class Workbook extends AbstractWorkbook
|
18 |
+
{
|
19 |
+
/**
|
20 |
+
* Maximum number of rows a ODS sheet can contain
|
21 |
+
* @see https://ask.libreoffice.org/en/question/8631/upper-limit-to-number-of-rows-in-calc/
|
22 |
+
*/
|
23 |
+
protected static $maxRowsPerWorksheet = 1048576;
|
24 |
+
|
25 |
+
/** @var \Box\Spout\Writer\ODS\Helper\FileSystemHelper Helper to perform file system operations */
|
26 |
+
protected $fileSystemHelper;
|
27 |
+
|
28 |
+
/** @var \Box\Spout\Writer\ODS\Helper\StyleHelper Helper to apply styles */
|
29 |
+
protected $styleHelper;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* @param string $tempFolder
|
33 |
+
* @param bool $shouldCreateNewSheetsAutomatically
|
34 |
+
* @param \Box\Spout\Writer\Style\Style $defaultRowStyle
|
35 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
|
36 |
+
*/
|
37 |
+
public function __construct($tempFolder, $shouldCreateNewSheetsAutomatically, $defaultRowStyle)
|
38 |
+
{
|
39 |
+
parent::__construct($shouldCreateNewSheetsAutomatically, $defaultRowStyle);
|
40 |
+
|
41 |
+
$this->fileSystemHelper = new FileSystemHelper($tempFolder);
|
42 |
+
$this->fileSystemHelper->createBaseFilesAndFolders();
|
43 |
+
|
44 |
+
$this->styleHelper = new StyleHelper($defaultRowStyle);
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @return \Box\Spout\Writer\ODS\Helper\StyleHelper Helper to apply styles to ODS files
|
49 |
+
*/
|
50 |
+
protected function getStyleHelper()
|
51 |
+
{
|
52 |
+
return $this->styleHelper;
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @return int Maximum number of rows/columns a sheet can contain
|
57 |
+
*/
|
58 |
+
protected function getMaxRowsPerWorksheet()
|
59 |
+
{
|
60 |
+
return self::$maxRowsPerWorksheet;
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Creates a new sheet in the workbook. The current sheet remains unchanged.
|
65 |
+
*
|
66 |
+
* @return Worksheet The created sheet
|
67 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
|
68 |
+
*/
|
69 |
+
public function addNewSheet()
|
70 |
+
{
|
71 |
+
$newSheetIndex = count($this->worksheets);
|
72 |
+
$sheet = new Sheet($newSheetIndex, $this->internalId);
|
73 |
+
|
74 |
+
$sheetsContentTempFolder = $this->fileSystemHelper->getSheetsContentTempFolder();
|
75 |
+
$worksheet = new Worksheet($sheet, $sheetsContentTempFolder);
|
76 |
+
$this->worksheets[] = $worksheet;
|
77 |
+
|
78 |
+
return $worksheet;
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Closes the workbook and all its associated sheets.
|
83 |
+
* All the necessary files are written to disk and zipped together to create the ODS file.
|
84 |
+
* All the temporary files are then deleted.
|
85 |
+
*
|
86 |
+
* @param resource $finalFilePointer Pointer to the ODS that will be created
|
87 |
+
* @return void
|
88 |
+
*/
|
89 |
+
public function close($finalFilePointer)
|
90 |
+
{
|
91 |
+
/** @var Worksheet[] $worksheets */
|
92 |
+
$worksheets = $this->worksheets;
|
93 |
+
$numWorksheets = count($worksheets);
|
94 |
+
|
95 |
+
foreach ($worksheets as $worksheet) {
|
96 |
+
$worksheet->close();
|
97 |
+
}
|
98 |
+
|
99 |
+
// Finish creating all the necessary files before zipping everything together
|
100 |
+
$this->fileSystemHelper
|
101 |
+
->createContentFile($worksheets, $this->styleHelper)
|
102 |
+
->deleteWorksheetTempFolder()
|
103 |
+
->createStylesFile($this->styleHelper, $numWorksheets)
|
104 |
+
->zipRootFolderAndCopyToStream($finalFilePointer);
|
105 |
+
|
106 |
+
$this->cleanupTempFolder();
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Deletes the root folder created in the temp folder and all its contents.
|
111 |
+
*
|
112 |
+
* @return void
|
113 |
+
*/
|
114 |
+
protected function cleanupTempFolder()
|
115 |
+
{
|
116 |
+
$xlsxRootFolder = $this->fileSystemHelper->getRootFolder();
|
117 |
+
$this->fileSystemHelper->deleteFolderRecursively($xlsxRootFolder);
|
118 |
+
}
|
119 |
+
}
|
app/Services/Spout/Writer/ODS/Internal/Worksheet.php
ADDED
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\ODS\Internal;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\InvalidArgumentException;
|
6 |
+
use Box\Spout\Common\Exception\IOException;
|
7 |
+
use Box\Spout\Common\Helper\StringHelper;
|
8 |
+
use Box\Spout\Writer\Common\Helper\CellHelper;
|
9 |
+
use Box\Spout\Writer\Common\Internal\WorksheetInterface;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Class Worksheet
|
13 |
+
* Represents a worksheet within a ODS file. The difference with the Sheet object is
|
14 |
+
* that this class provides an interface to write data
|
15 |
+
*
|
16 |
+
* @package Box\Spout\Writer\ODS\Internal
|
17 |
+
*/
|
18 |
+
class Worksheet implements WorksheetInterface
|
19 |
+
{
|
20 |
+
/** @var \Box\Spout\Writer\Common\Sheet The "external" sheet */
|
21 |
+
protected $externalSheet;
|
22 |
+
|
23 |
+
/** @var string Path to the XML file that will contain the sheet data */
|
24 |
+
protected $worksheetFilePath;
|
25 |
+
|
26 |
+
/** @var \Box\Spout\Common\Escaper\ODS Strings escaper */
|
27 |
+
protected $stringsEscaper;
|
28 |
+
|
29 |
+
/** @var \Box\Spout\Common\Helper\StringHelper To help with string manipulation */
|
30 |
+
protected $stringHelper;
|
31 |
+
|
32 |
+
/** @var Resource Pointer to the temporary sheet data file (e.g. worksheets-temp/sheet1.xml) */
|
33 |
+
protected $sheetFilePointer;
|
34 |
+
|
35 |
+
/** @var int Maximum number of columns among all the written rows */
|
36 |
+
protected $maxNumColumns = 1;
|
37 |
+
|
38 |
+
/** @var int Index of the last written row */
|
39 |
+
protected $lastWrittenRowIndex = 0;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* @param \Box\Spout\Writer\Common\Sheet $externalSheet The associated "external" sheet
|
43 |
+
* @param string $worksheetFilesFolder Temporary folder where the files to create the ODS will be stored
|
44 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
45 |
+
*/
|
46 |
+
public function __construct($externalSheet, $worksheetFilesFolder)
|
47 |
+
{
|
48 |
+
$this->externalSheet = $externalSheet;
|
49 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
50 |
+
$this->stringsEscaper = \Box\Spout\Common\Escaper\ODS::getInstance();
|
51 |
+
$this->worksheetFilePath = $worksheetFilesFolder . '/sheet' . $externalSheet->getIndex() . '.xml';
|
52 |
+
|
53 |
+
$this->stringHelper = new StringHelper();
|
54 |
+
|
55 |
+
$this->startSheet();
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Prepares the worksheet to accept data
|
60 |
+
* The XML file does not contain the "<table:table>" node as it contains the sheet's name
|
61 |
+
* which may change during the execution of the program. It will be added at the end.
|
62 |
+
*
|
63 |
+
* @return void
|
64 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
65 |
+
*/
|
66 |
+
protected function startSheet()
|
67 |
+
{
|
68 |
+
$this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
|
69 |
+
$this->throwIfSheetFilePointerIsNotAvailable();
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Checks if the book has been created. Throws an exception if not created yet.
|
74 |
+
*
|
75 |
+
* @return void
|
76 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
77 |
+
*/
|
78 |
+
protected function throwIfSheetFilePointerIsNotAvailable()
|
79 |
+
{
|
80 |
+
if (!$this->sheetFilePointer) {
|
81 |
+
throw new IOException('Unable to open sheet for writing.');
|
82 |
+
}
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* @return string Path to the temporary sheet content XML file
|
87 |
+
*/
|
88 |
+
public function getWorksheetFilePath()
|
89 |
+
{
|
90 |
+
return $this->worksheetFilePath;
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Returns the table XML root node as string.
|
95 |
+
*
|
96 |
+
* @return string <table> node as string
|
97 |
+
*/
|
98 |
+
public function getTableElementStartAsString()
|
99 |
+
{
|
100 |
+
$escapedSheetName = $this->stringsEscaper->escape($this->externalSheet->getName());
|
101 |
+
$tableStyleName = 'ta' . ($this->externalSheet->getIndex() + 1);
|
102 |
+
|
103 |
+
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
|
104 |
+
$tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $this->maxNumColumns . '"/>';
|
105 |
+
|
106 |
+
return $tableElement;
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* @return \Box\Spout\Writer\Common\Sheet The "external" sheet
|
111 |
+
*/
|
112 |
+
public function getExternalSheet()
|
113 |
+
{
|
114 |
+
return $this->externalSheet;
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* @return int The index of the last written row
|
119 |
+
*/
|
120 |
+
public function getLastWrittenRowIndex()
|
121 |
+
{
|
122 |
+
return $this->lastWrittenRowIndex;
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Adds data to the worksheet.
|
127 |
+
*
|
128 |
+
* @param array $dataRow Array containing data to be written. Cannot be empty.
|
129 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
130 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style.
|
131 |
+
* @return void
|
132 |
+
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
|
133 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
134 |
+
*/
|
135 |
+
public function addRow($dataRow, $style)
|
136 |
+
{
|
137 |
+
// $dataRow can be an associative array. We need to transform
|
138 |
+
// it into a regular array, as we'll use the numeric indexes.
|
139 |
+
$dataRowWithNumericIndexes = array_values($dataRow);
|
140 |
+
|
141 |
+
$styleIndex = ($style->getId() + 1); // 1-based
|
142 |
+
$cellsCount = count($dataRow);
|
143 |
+
$this->maxNumColumns = max($this->maxNumColumns, $cellsCount);
|
144 |
+
|
145 |
+
$data = '<table:table-row table:style-name="ro1">';
|
146 |
+
|
147 |
+
$currentCellIndex = 0;
|
148 |
+
$nextCellIndex = 1;
|
149 |
+
|
150 |
+
for ($i = 0; $i < $cellsCount; $i++) {
|
151 |
+
$currentCellValue = $dataRowWithNumericIndexes[$currentCellIndex];
|
152 |
+
|
153 |
+
// Using isset here because it is way faster than array_key_exists...
|
154 |
+
if (!isset($dataRowWithNumericIndexes[$nextCellIndex]) ||
|
155 |
+
$currentCellValue !== $dataRowWithNumericIndexes[$nextCellIndex]) {
|
156 |
+
|
157 |
+
$numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
|
158 |
+
$data .= $this->getCellXML($currentCellValue, $styleIndex, $numTimesValueRepeated);
|
159 |
+
|
160 |
+
$currentCellIndex = $nextCellIndex;
|
161 |
+
}
|
162 |
+
|
163 |
+
$nextCellIndex++;
|
164 |
+
}
|
165 |
+
|
166 |
+
$data .= '</table:table-row>';
|
167 |
+
|
168 |
+
$wasWriteSuccessful = fwrite($this->sheetFilePointer, $data);
|
169 |
+
if ($wasWriteSuccessful === false) {
|
170 |
+
throw new IOException("Unable to write data in {$this->worksheetFilePath}");
|
171 |
+
}
|
172 |
+
|
173 |
+
// only update the count if the write worked
|
174 |
+
$this->lastWrittenRowIndex++;
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Returns the cell XML content, given its value.
|
179 |
+
*
|
180 |
+
* @param mixed $cellValue The value to be written
|
181 |
+
* @param int $styleIndex Index of the used style
|
182 |
+
* @param int $numTimesValueRepeated Number of times the value is consecutively repeated
|
183 |
+
* @return string The cell XML content
|
184 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
185 |
+
*/
|
186 |
+
protected function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
|
187 |
+
{
|
188 |
+
$data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
|
189 |
+
|
190 |
+
if ($numTimesValueRepeated !== 1) {
|
191 |
+
$data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
|
192 |
+
}
|
193 |
+
|
194 |
+
if (CellHelper::isNonEmptyString($cellValue)) {
|
195 |
+
$data .= ' office:value-type="string" calcext:value-type="string">';
|
196 |
+
|
197 |
+
$cellValueLines = explode("\n", $cellValue);
|
198 |
+
foreach ($cellValueLines as $cellValueLine) {
|
199 |
+
$data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
|
200 |
+
}
|
201 |
+
|
202 |
+
$data .= '</table:table-cell>';
|
203 |
+
} else if (CellHelper::isBoolean($cellValue)) {
|
204 |
+
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cellValue . '">';
|
205 |
+
$data .= '<text:p>' . $cellValue . '</text:p>';
|
206 |
+
$data .= '</table:table-cell>';
|
207 |
+
} else if (CellHelper::isNumeric($cellValue)) {
|
208 |
+
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">';
|
209 |
+
$data .= '<text:p>' . $cellValue . '</text:p>';
|
210 |
+
$data .= '</table:table-cell>';
|
211 |
+
} else if (empty($cellValue)) {
|
212 |
+
$data .= '/>';
|
213 |
+
} else {
|
214 |
+
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue));
|
215 |
+
}
|
216 |
+
|
217 |
+
return $data;
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* Closes the worksheet
|
222 |
+
*
|
223 |
+
* @return void
|
224 |
+
*/
|
225 |
+
public function close()
|
226 |
+
{
|
227 |
+
if (!is_resource($this->sheetFilePointer)) {
|
228 |
+
return;
|
229 |
+
}
|
230 |
+
|
231 |
+
fclose($this->sheetFilePointer);
|
232 |
+
}
|
233 |
+
}
|
app/Services/Spout/Writer/ODS/Writer.php
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\ODS;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\AbstractMultiSheetsWriter;
|
6 |
+
use Box\Spout\Writer\Common;
|
7 |
+
use Box\Spout\Writer\ODS\Internal\Workbook;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class Writer
|
11 |
+
* This class provides base support to write data to ODS files
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Writer\ODS
|
14 |
+
*/
|
15 |
+
class Writer extends AbstractMultiSheetsWriter
|
16 |
+
{
|
17 |
+
/** @var string Content-Type value for the header */
|
18 |
+
protected static $headerContentType = 'application/vnd.oasis.opendocument.spreadsheet';
|
19 |
+
|
20 |
+
/** @var string Temporary folder where the files to create the ODS will be stored */
|
21 |
+
protected $tempFolder;
|
22 |
+
|
23 |
+
/** @var Internal\Workbook The workbook for the ODS file */
|
24 |
+
protected $book;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Sets a custom temporary folder for creating intermediate files/folders.
|
28 |
+
* This must be set before opening the writer.
|
29 |
+
*
|
30 |
+
* @api
|
31 |
+
* @param string $tempFolder Temporary folder where the files to create the ODS will be stored
|
32 |
+
* @return Writer
|
33 |
+
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
|
34 |
+
*/
|
35 |
+
public function setTempFolder($tempFolder)
|
36 |
+
{
|
37 |
+
$this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
|
38 |
+
|
39 |
+
$this->tempFolder = $tempFolder;
|
40 |
+
return $this;
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Configures the write and sets the current sheet pointer to a new sheet.
|
45 |
+
*
|
46 |
+
* @return void
|
47 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the file for writing
|
48 |
+
*/
|
49 |
+
protected function openWriter()
|
50 |
+
{
|
51 |
+
$tempFolder = ($this->tempFolder) ? : sys_get_temp_dir();
|
52 |
+
$this->book = new Workbook($tempFolder, $this->shouldCreateNewSheetsAutomatically, $this->defaultRowStyle);
|
53 |
+
$this->book->addNewSheetAndMakeItCurrent();
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* @return Internal\Workbook The workbook representing the file to be written
|
58 |
+
*/
|
59 |
+
protected function getWorkbook()
|
60 |
+
{
|
61 |
+
return $this->book;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Adds data to the currently opened writer.
|
66 |
+
* If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination
|
67 |
+
* with the creation of new worksheets if one worksheet has reached its maximum capicity.
|
68 |
+
*
|
69 |
+
* @param array $dataRow Array containing data to be written.
|
70 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
71 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
|
72 |
+
* @return void
|
73 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the book is not created yet
|
74 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
75 |
+
*/
|
76 |
+
protected function addRowToWriter(array $dataRow, $style)
|
77 |
+
{
|
78 |
+
$this->throwIfBookIsNotAvailable();
|
79 |
+
$this->book->addRowToCurrentWorksheet($dataRow, $style);
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Closes the writer, preventing any additional writing.
|
84 |
+
*
|
85 |
+
* @return void
|
86 |
+
*/
|
87 |
+
protected function closeWriter()
|
88 |
+
{
|
89 |
+
if ($this->book) {
|
90 |
+
$this->book->close($this->filePointer);
|
91 |
+
}
|
92 |
+
}
|
93 |
+
}
|
app/Services/Spout/Writer/Style/Border.php
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Style;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class Border
|
7 |
+
*/
|
8 |
+
class Border
|
9 |
+
{
|
10 |
+
const LEFT = 'left';
|
11 |
+
const RIGHT = 'right';
|
12 |
+
const TOP = 'top';
|
13 |
+
const BOTTOM = 'bottom';
|
14 |
+
|
15 |
+
const STYLE_NONE = 'none';
|
16 |
+
const STYLE_SOLID = 'solid';
|
17 |
+
const STYLE_DASHED = 'dashed';
|
18 |
+
const STYLE_DOTTED = 'dotted';
|
19 |
+
const STYLE_DOUBLE = 'double';
|
20 |
+
|
21 |
+
const WIDTH_THIN = 'thin';
|
22 |
+
const WIDTH_MEDIUM = 'medium';
|
23 |
+
const WIDTH_THICK = 'thick';
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @var array A list of BorderPart objects for this border.
|
27 |
+
*/
|
28 |
+
protected $parts = [];
|
29 |
+
|
30 |
+
/**
|
31 |
+
* @param array|void $borderParts
|
32 |
+
*/
|
33 |
+
public function __construct(array $borderParts = [])
|
34 |
+
{
|
35 |
+
$this->setParts($borderParts);
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @param $name The name of the border part
|
40 |
+
* @return null|BorderPart
|
41 |
+
*/
|
42 |
+
public function getPart($name)
|
43 |
+
{
|
44 |
+
return $this->hasPart($name) ? $this->parts[$name] : null;
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @param $name The name of the border part
|
49 |
+
* @return bool
|
50 |
+
*/
|
51 |
+
public function hasPart($name)
|
52 |
+
{
|
53 |
+
return isset($this->parts[$name]);
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* @return array
|
58 |
+
*/
|
59 |
+
public function getParts()
|
60 |
+
{
|
61 |
+
return $this->parts;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Set BorderParts
|
66 |
+
* @param array $parts
|
67 |
+
*/
|
68 |
+
public function setParts($parts)
|
69 |
+
{
|
70 |
+
unset($this->parts);
|
71 |
+
foreach ($parts as $part) {
|
72 |
+
$this->addPart($part);
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* @param BorderPart $borderPart
|
78 |
+
* @return self
|
79 |
+
*/
|
80 |
+
public function addPart(BorderPart $borderPart)
|
81 |
+
{
|
82 |
+
$this->parts[$borderPart->getName()] = $borderPart;
|
83 |
+
return $this;
|
84 |
+
}
|
85 |
+
}
|
app/Services/Spout/Writer/Style/BorderBuilder.php
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Style;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class BorderBuilder
|
7 |
+
*/
|
8 |
+
class BorderBuilder
|
9 |
+
{
|
10 |
+
/**
|
11 |
+
* @var Border
|
12 |
+
*/
|
13 |
+
protected $border;
|
14 |
+
|
15 |
+
public function __construct()
|
16 |
+
{
|
17 |
+
$this->border = new Border();
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* @param string|void $color Border A RGB color code
|
22 |
+
* @param string|void $width Border width @see BorderPart::allowedWidths
|
23 |
+
* @param string|void $style Border style @see BorderPart::allowedStyles
|
24 |
+
* @return BorderBuilder
|
25 |
+
*/
|
26 |
+
public function setBorderTop($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
|
27 |
+
{
|
28 |
+
$this->border->addPart(new BorderPart(Border::TOP, $color, $width, $style));
|
29 |
+
return $this;
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @param string|void $color Border A RGB color code
|
34 |
+
* @param string|void $width Border width @see BorderPart::allowedWidths
|
35 |
+
* @param string|void $style Border style @see BorderPart::allowedStyles
|
36 |
+
* @return BorderBuilder
|
37 |
+
*/
|
38 |
+
public function setBorderRight($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
|
39 |
+
{
|
40 |
+
$this->border->addPart(new BorderPart(Border::RIGHT, $color, $width, $style));
|
41 |
+
return $this;
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @param string|void $color Border A RGB color code
|
46 |
+
* @param string|void $width Border width @see BorderPart::allowedWidths
|
47 |
+
* @param string|void $style Border style @see BorderPart::allowedStyles
|
48 |
+
* @return BorderBuilder
|
49 |
+
*/
|
50 |
+
public function setBorderBottom($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
|
51 |
+
{
|
52 |
+
$this->border->addPart(new BorderPart(Border::BOTTOM, $color, $width, $style));
|
53 |
+
return $this;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* @param string|void $color Border A RGB color code
|
58 |
+
* @param string|void $width Border width @see BorderPart::allowedWidths
|
59 |
+
* @param string|void $style Border style @see BorderPart::allowedStyles
|
60 |
+
* @return BorderBuilder
|
61 |
+
*/
|
62 |
+
public function setBorderLeft($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
|
63 |
+
{
|
64 |
+
$this->border->addPart(new BorderPart(Border::LEFT, $color, $width, $style));
|
65 |
+
return $this;
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* @return Border
|
70 |
+
*/
|
71 |
+
public function build()
|
72 |
+
{
|
73 |
+
return $this->border;
|
74 |
+
}
|
75 |
+
}
|
app/Services/Spout/Writer/Style/BorderPart.php
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Style;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\Border\InvalidNameException;
|
6 |
+
use Box\Spout\Writer\Exception\Border\InvalidStyleException;
|
7 |
+
use Box\Spout\Writer\Exception\Border\InvalidWidthException;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class BorderPart
|
11 |
+
*/
|
12 |
+
class BorderPart
|
13 |
+
{
|
14 |
+
/**
|
15 |
+
* @var string The style of this border part.
|
16 |
+
*/
|
17 |
+
protected $style;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @var string The name of this border part.
|
21 |
+
*/
|
22 |
+
protected $name;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @var string The color of this border part.
|
26 |
+
*/
|
27 |
+
protected $color;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @var string The width of this border part.
|
31 |
+
*/
|
32 |
+
protected $width;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* @var array Allowed style constants for parts.
|
36 |
+
*/
|
37 |
+
protected static $allowedStyles = [
|
38 |
+
'none',
|
39 |
+
'solid',
|
40 |
+
'dashed',
|
41 |
+
'dotted',
|
42 |
+
'double'
|
43 |
+
];
|
44 |
+
|
45 |
+
/**
|
46 |
+
* @var array Allowed names constants for border parts.
|
47 |
+
*/
|
48 |
+
protected static $allowedNames = [
|
49 |
+
'left',
|
50 |
+
'right',
|
51 |
+
'top',
|
52 |
+
'bottom',
|
53 |
+
];
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @var array Allowed width constants for border parts.
|
57 |
+
*/
|
58 |
+
protected static $allowedWidths = [
|
59 |
+
'thin',
|
60 |
+
'medium',
|
61 |
+
'thick',
|
62 |
+
];
|
63 |
+
|
64 |
+
/**
|
65 |
+
* @param string $name @see BorderPart::$allowedNames
|
66 |
+
* @param string $color A RGB color code
|
67 |
+
* @param string $width @see BorderPart::$allowedWidths
|
68 |
+
* @param string $style @see BorderPart::$allowedStyles
|
69 |
+
* @throws InvalidNameException
|
70 |
+
* @throws InvalidStyleException
|
71 |
+
* @throws InvalidWidthException
|
72 |
+
*/
|
73 |
+
public function __construct($name, $color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
|
74 |
+
{
|
75 |
+
$this->setName($name);
|
76 |
+
$this->setColor($color);
|
77 |
+
$this->setWidth($width);
|
78 |
+
$this->setStyle($style);
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* @return string
|
83 |
+
*/
|
84 |
+
public function getName()
|
85 |
+
{
|
86 |
+
return $this->name;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* @param string $name The name of the border part @see BorderPart::$allowedNames
|
91 |
+
* @throws InvalidNameException
|
92 |
+
* @return void
|
93 |
+
*/
|
94 |
+
public function setName($name)
|
95 |
+
{
|
96 |
+
if (!in_array($name, self::$allowedNames)) {
|
97 |
+
throw new InvalidNameException($name);
|
98 |
+
}
|
99 |
+
$this->name = $name;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* @return string
|
104 |
+
*/
|
105 |
+
public function getStyle()
|
106 |
+
{
|
107 |
+
return $this->style;
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* @param string $style The style of the border part @see BorderPart::$allowedStyles
|
112 |
+
* @throws InvalidStyleException
|
113 |
+
* @return void
|
114 |
+
*/
|
115 |
+
public function setStyle($style)
|
116 |
+
{
|
117 |
+
if (!in_array($style, self::$allowedStyles)) {
|
118 |
+
throw new InvalidStyleException($style);
|
119 |
+
}
|
120 |
+
$this->style = $style;
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* @return string
|
125 |
+
*/
|
126 |
+
public function getColor()
|
127 |
+
{
|
128 |
+
return $this->color;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* @param string $color The color of the border part @see Color::rgb()
|
133 |
+
* @return void
|
134 |
+
*/
|
135 |
+
public function setColor($color)
|
136 |
+
{
|
137 |
+
$this->color = $color;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* @return string
|
142 |
+
*/
|
143 |
+
public function getWidth()
|
144 |
+
{
|
145 |
+
return $this->width;
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* @param string $width The width of the border part @see BorderPart::$allowedWidths
|
150 |
+
* @throws InvalidWidthException
|
151 |
+
* @return void
|
152 |
+
*/
|
153 |
+
public function setWidth($width)
|
154 |
+
{
|
155 |
+
if (!in_array($width, self::$allowedWidths)) {
|
156 |
+
throw new InvalidWidthException($width);
|
157 |
+
}
|
158 |
+
$this->width = $width;
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* @return array
|
163 |
+
*/
|
164 |
+
public static function getAllowedStyles()
|
165 |
+
{
|
166 |
+
return self::$allowedStyles;
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* @return array
|
171 |
+
*/
|
172 |
+
public static function getAllowedNames()
|
173 |
+
{
|
174 |
+
return self::$allowedNames;
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* @return array
|
179 |
+
*/
|
180 |
+
public static function getAllowedWidths()
|
181 |
+
{
|
182 |
+
return self::$allowedWidths;
|
183 |
+
}
|
184 |
+
}
|
app/Services/Spout/Writer/Style/Color.php
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Style;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Exception\InvalidColorException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class Color
|
9 |
+
* This class provides constants and functions to work with colors
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Writer\Style
|
12 |
+
*/
|
13 |
+
class Color
|
14 |
+
{
|
15 |
+
/** Standard colors - based on Office Online */
|
16 |
+
const BLACK = '000000';
|
17 |
+
const WHITE = 'FFFFFF';
|
18 |
+
const RED = 'FF0000';
|
19 |
+
const DARK_RED = 'C00000';
|
20 |
+
const ORANGE = 'FFC000';
|
21 |
+
const YELLOW = 'FFFF00';
|
22 |
+
const LIGHT_GREEN = '92D040';
|
23 |
+
const GREEN = '00B050';
|
24 |
+
const LIGHT_BLUE = '00B0E0';
|
25 |
+
const BLUE = '0070C0';
|
26 |
+
const DARK_BLUE = '002060';
|
27 |
+
const PURPLE = '7030A0';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Returns an RGB color from R, G and B values
|
31 |
+
*
|
32 |
+
* @api
|
33 |
+
* @param int $red Red component, 0 - 255
|
34 |
+
* @param int $green Green component, 0 - 255
|
35 |
+
* @param int $blue Blue component, 0 - 255
|
36 |
+
* @return string RGB color
|
37 |
+
*/
|
38 |
+
public static function rgb($red, $green, $blue)
|
39 |
+
{
|
40 |
+
self::throwIfInvalidColorComponentValue($red);
|
41 |
+
self::throwIfInvalidColorComponentValue($green);
|
42 |
+
self::throwIfInvalidColorComponentValue($blue);
|
43 |
+
|
44 |
+
return strtoupper(
|
45 |
+
self::convertColorComponentToHex($red) .
|
46 |
+
self::convertColorComponentToHex($green) .
|
47 |
+
self::convertColorComponentToHex($blue)
|
48 |
+
);
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Throws an exception is the color component value is outside of bounds (0 - 255)
|
53 |
+
*
|
54 |
+
* @param int $colorComponent
|
55 |
+
* @return void
|
56 |
+
* @throws \Box\Spout\Writer\Exception\InvalidColorException
|
57 |
+
*/
|
58 |
+
protected static function throwIfInvalidColorComponentValue($colorComponent)
|
59 |
+
{
|
60 |
+
if (!is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) {
|
61 |
+
throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Converts the color component to its corresponding hexadecimal value
|
67 |
+
*
|
68 |
+
* @param int $colorComponent Color component, 0 - 255
|
69 |
+
* @return string Corresponding hexadecimal value, with a leading 0 if needed. E.g "0f", "2d"
|
70 |
+
*/
|
71 |
+
protected static function convertColorComponentToHex($colorComponent)
|
72 |
+
{
|
73 |
+
return str_pad(dechex($colorComponent), 2, '0', STR_PAD_LEFT);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Returns the ARGB color of the given RGB color,
|
78 |
+
* assuming that alpha value is always 1.
|
79 |
+
*
|
80 |
+
* @param string $rgbColor RGB color like "FF08B2"
|
81 |
+
* @return string ARGB color
|
82 |
+
*/
|
83 |
+
public static function toARGB($rgbColor)
|
84 |
+
{
|
85 |
+
return 'FF' . $rgbColor;
|
86 |
+
}
|
87 |
+
}
|
app/Services/Spout/Writer/Style/Style.php
ADDED
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Style;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class Style
|
7 |
+
* Represents a style to be applied to a cell
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Writer\Style
|
10 |
+
*/
|
11 |
+
class Style
|
12 |
+
{
|
13 |
+
/** Default font values */
|
14 |
+
const DEFAULT_FONT_SIZE = 11;
|
15 |
+
const DEFAULT_FONT_COLOR = Color::BLACK;
|
16 |
+
const DEFAULT_FONT_NAME = 'Arial';
|
17 |
+
|
18 |
+
/** @var int|null Style ID */
|
19 |
+
protected $id = null;
|
20 |
+
|
21 |
+
/** @var bool Whether the font should be bold */
|
22 |
+
protected $fontBold = false;
|
23 |
+
/** @var bool Whether the bold property was set */
|
24 |
+
protected $hasSetFontBold = false;
|
25 |
+
|
26 |
+
/** @var bool Whether the font should be italic */
|
27 |
+
protected $fontItalic = false;
|
28 |
+
/** @var bool Whether the italic property was set */
|
29 |
+
protected $hasSetFontItalic = false;
|
30 |
+
|
31 |
+
/** @var bool Whether the font should be underlined */
|
32 |
+
protected $fontUnderline = false;
|
33 |
+
/** @var bool Whether the underline property was set */
|
34 |
+
protected $hasSetFontUnderline = false;
|
35 |
+
|
36 |
+
/** @var bool Whether the font should be struck through */
|
37 |
+
protected $fontStrikethrough = false;
|
38 |
+
/** @var bool Whether the strikethrough property was set */
|
39 |
+
protected $hasSetFontStrikethrough = false;
|
40 |
+
|
41 |
+
/** @var int Font size */
|
42 |
+
protected $fontSize = self::DEFAULT_FONT_SIZE;
|
43 |
+
/** @var bool Whether the font size property was set */
|
44 |
+
protected $hasSetFontSize = false;
|
45 |
+
|
46 |
+
/** @var string Font color */
|
47 |
+
protected $fontColor = self::DEFAULT_FONT_COLOR;
|
48 |
+
/** @var bool Whether the font color property was set */
|
49 |
+
protected $hasSetFontColor = false;
|
50 |
+
|
51 |
+
/** @var string Font name */
|
52 |
+
protected $fontName = self::DEFAULT_FONT_NAME;
|
53 |
+
/** @var bool Whether the font name property was set */
|
54 |
+
protected $hasSetFontName = false;
|
55 |
+
|
56 |
+
/** @var bool Whether specific font properties should be applied */
|
57 |
+
protected $shouldApplyFont = false;
|
58 |
+
|
59 |
+
/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
|
60 |
+
protected $shouldWrapText = false;
|
61 |
+
/** @var bool Whether the wrap text property was set */
|
62 |
+
protected $hasSetWrapText = false;
|
63 |
+
|
64 |
+
/**
|
65 |
+
* @var Border
|
66 |
+
*/
|
67 |
+
protected $border = null;
|
68 |
+
|
69 |
+
/**
|
70 |
+
* @var bool Whether border properties should be applied
|
71 |
+
*/
|
72 |
+
protected $shouldApplyBorder = false;
|
73 |
+
|
74 |
+
/** @var string Background color */
|
75 |
+
protected $backgroundColor = null;
|
76 |
+
|
77 |
+
/** @var bool */
|
78 |
+
protected $hasSetBackgroundColor = false;
|
79 |
+
|
80 |
+
|
81 |
+
/**
|
82 |
+
* @return int|null
|
83 |
+
*/
|
84 |
+
public function getId()
|
85 |
+
{
|
86 |
+
return $this->id;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* @param int $id
|
91 |
+
* @return Style
|
92 |
+
*/
|
93 |
+
public function setId($id)
|
94 |
+
{
|
95 |
+
$this->id = $id;
|
96 |
+
return $this;
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* @return Border
|
101 |
+
*/
|
102 |
+
public function getBorder()
|
103 |
+
{
|
104 |
+
return $this->border;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* @param Border $border
|
109 |
+
* @return Style
|
110 |
+
*/
|
111 |
+
public function setBorder(Border $border)
|
112 |
+
{
|
113 |
+
$this->shouldApplyBorder = true;
|
114 |
+
$this->border = $border;
|
115 |
+
return $this;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* @return bool
|
120 |
+
*/
|
121 |
+
public function shouldApplyBorder()
|
122 |
+
{
|
123 |
+
return $this->shouldApplyBorder;
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* @return bool
|
128 |
+
*/
|
129 |
+
public function isFontBold()
|
130 |
+
{
|
131 |
+
return $this->fontBold;
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* @return Style
|
136 |
+
*/
|
137 |
+
public function setFontBold()
|
138 |
+
{
|
139 |
+
$this->fontBold = true;
|
140 |
+
$this->hasSetFontBold = true;
|
141 |
+
$this->shouldApplyFont = true;
|
142 |
+
return $this;
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* @return bool
|
147 |
+
*/
|
148 |
+
public function isFontItalic()
|
149 |
+
{
|
150 |
+
return $this->fontItalic;
|
151 |
+
}
|
152 |
+
|
153 |
+
/**
|
154 |
+
* @return Style
|
155 |
+
*/
|
156 |
+
public function setFontItalic()
|
157 |
+
{
|
158 |
+
$this->fontItalic = true;
|
159 |
+
$this->hasSetFontItalic = true;
|
160 |
+
$this->shouldApplyFont = true;
|
161 |
+
return $this;
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* @return bool
|
166 |
+
*/
|
167 |
+
public function isFontUnderline()
|
168 |
+
{
|
169 |
+
return $this->fontUnderline;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* @return Style
|
174 |
+
*/
|
175 |
+
public function setFontUnderline()
|
176 |
+
{
|
177 |
+
$this->fontUnderline = true;
|
178 |
+
$this->hasSetFontUnderline = true;
|
179 |
+
$this->shouldApplyFont = true;
|
180 |
+
return $this;
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* @return bool
|
185 |
+
*/
|
186 |
+
public function isFontStrikethrough()
|
187 |
+
{
|
188 |
+
return $this->fontStrikethrough;
|
189 |
+
}
|
190 |
+
|
191 |
+
/**
|
192 |
+
* @return Style
|
193 |
+
*/
|
194 |
+
public function setFontStrikethrough()
|
195 |
+
{
|
196 |
+
$this->fontStrikethrough = true;
|
197 |
+
$this->hasSetFontStrikethrough = true;
|
198 |
+
$this->shouldApplyFont = true;
|
199 |
+
return $this;
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* @return int
|
204 |
+
*/
|
205 |
+
public function getFontSize()
|
206 |
+
{
|
207 |
+
return $this->fontSize;
|
208 |
+
}
|
209 |
+
|
210 |
+
/**
|
211 |
+
* @param int $fontSize Font size, in pixels
|
212 |
+
* @return Style
|
213 |
+
*/
|
214 |
+
public function setFontSize($fontSize)
|
215 |
+
{
|
216 |
+
$this->fontSize = $fontSize;
|
217 |
+
$this->hasSetFontSize = true;
|
218 |
+
$this->shouldApplyFont = true;
|
219 |
+
return $this;
|
220 |
+
}
|
221 |
+
|
222 |
+
/**
|
223 |
+
* @return string
|
224 |
+
*/
|
225 |
+
public function getFontColor()
|
226 |
+
{
|
227 |
+
return $this->fontColor;
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Sets the font color.
|
232 |
+
*
|
233 |
+
* @param string $fontColor ARGB color (@see Color)
|
234 |
+
* @return Style
|
235 |
+
*/
|
236 |
+
public function setFontColor($fontColor)
|
237 |
+
{
|
238 |
+
$this->fontColor = $fontColor;
|
239 |
+
$this->hasSetFontColor = true;
|
240 |
+
$this->shouldApplyFont = true;
|
241 |
+
return $this;
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* @return string
|
246 |
+
*/
|
247 |
+
public function getFontName()
|
248 |
+
{
|
249 |
+
return $this->fontName;
|
250 |
+
}
|
251 |
+
|
252 |
+
/**
|
253 |
+
* @param string $fontName Name of the font to use
|
254 |
+
* @return Style
|
255 |
+
*/
|
256 |
+
public function setFontName($fontName)
|
257 |
+
{
|
258 |
+
$this->fontName = $fontName;
|
259 |
+
$this->hasSetFontName = true;
|
260 |
+
$this->shouldApplyFont = true;
|
261 |
+
return $this;
|
262 |
+
}
|
263 |
+
|
264 |
+
/**
|
265 |
+
* @return bool
|
266 |
+
*/
|
267 |
+
public function shouldWrapText()
|
268 |
+
{
|
269 |
+
return $this->shouldWrapText;
|
270 |
+
}
|
271 |
+
|
272 |
+
/**
|
273 |
+
* @param bool|void $shouldWrap Should the text be wrapped
|
274 |
+
* @return Style
|
275 |
+
*/
|
276 |
+
public function setShouldWrapText($shouldWrap = true)
|
277 |
+
{
|
278 |
+
$this->shouldWrapText = $shouldWrap;
|
279 |
+
$this->hasSetWrapText = true;
|
280 |
+
return $this;
|
281 |
+
}
|
282 |
+
|
283 |
+
/**
|
284 |
+
* @return bool
|
285 |
+
*/
|
286 |
+
public function hasSetWrapText()
|
287 |
+
{
|
288 |
+
return $this->hasSetWrapText;
|
289 |
+
}
|
290 |
+
|
291 |
+
/**
|
292 |
+
* @return bool Whether specific font properties should be applied
|
293 |
+
*/
|
294 |
+
public function shouldApplyFont()
|
295 |
+
{
|
296 |
+
return $this->shouldApplyFont;
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Sets the background color
|
301 |
+
* @param string $color ARGB color (@see Color)
|
302 |
+
* @return Style
|
303 |
+
*/
|
304 |
+
public function setBackgroundColor($color)
|
305 |
+
{
|
306 |
+
$this->hasSetBackgroundColor = true;
|
307 |
+
$this->backgroundColor = $color;
|
308 |
+
return $this;
|
309 |
+
}
|
310 |
+
|
311 |
+
/**
|
312 |
+
* @return string
|
313 |
+
*/
|
314 |
+
public function getBackgroundColor()
|
315 |
+
{
|
316 |
+
return $this->backgroundColor;
|
317 |
+
}
|
318 |
+
|
319 |
+
/**
|
320 |
+
*
|
321 |
+
* @return bool Whether the background color should be applied
|
322 |
+
*/
|
323 |
+
public function shouldApplyBackgroundColor()
|
324 |
+
{
|
325 |
+
return $this->hasSetBackgroundColor;
|
326 |
+
}
|
327 |
+
|
328 |
+
/**
|
329 |
+
* Serializes the style for future comparison with other styles.
|
330 |
+
* The ID is excluded from the comparison, as we only care about
|
331 |
+
* actual style properties.
|
332 |
+
*
|
333 |
+
* @return string The serialized style
|
334 |
+
*/
|
335 |
+
public function serialize()
|
336 |
+
{
|
337 |
+
// In order to be able to properly compare style, set static ID value
|
338 |
+
$currentId = $this->id;
|
339 |
+
$this->setId(0);
|
340 |
+
|
341 |
+
$serializedStyle = serialize($this);
|
342 |
+
|
343 |
+
$this->setId($currentId);
|
344 |
+
|
345 |
+
return $serializedStyle;
|
346 |
+
}
|
347 |
+
|
348 |
+
/**
|
349 |
+
* Merges the current style with the given style, using the given style as a base. This means that:
|
350 |
+
* - if current style and base style both have property A set, use current style property's value
|
351 |
+
* - if current style has property A set but base style does not, use current style property's value
|
352 |
+
* - if base style has property A set but current style does not, use base style property's value
|
353 |
+
*
|
354 |
+
* @NOTE: This function returns a new style.
|
355 |
+
*
|
356 |
+
* @param Style $baseStyle
|
357 |
+
* @return Style New style corresponding to the merge of the 2 styles
|
358 |
+
*/
|
359 |
+
public function mergeWith($baseStyle)
|
360 |
+
{
|
361 |
+
$mergedStyle = clone $this;
|
362 |
+
|
363 |
+
$this->mergeFontStyles($mergedStyle, $baseStyle);
|
364 |
+
$this->mergeOtherFontProperties($mergedStyle, $baseStyle);
|
365 |
+
$this->mergeCellProperties($mergedStyle, $baseStyle);
|
366 |
+
|
367 |
+
return $mergedStyle;
|
368 |
+
}
|
369 |
+
|
370 |
+
/**
|
371 |
+
* @param Style $styleToUpdate (passed as reference)
|
372 |
+
* @param Style $baseStyle
|
373 |
+
* @return void
|
374 |
+
*/
|
375 |
+
private function mergeFontStyles($styleToUpdate, $baseStyle)
|
376 |
+
{
|
377 |
+
if (!$this->hasSetFontBold && $baseStyle->isFontBold()) {
|
378 |
+
$styleToUpdate->setFontBold();
|
379 |
+
}
|
380 |
+
if (!$this->hasSetFontItalic && $baseStyle->isFontItalic()) {
|
381 |
+
$styleToUpdate->setFontItalic();
|
382 |
+
}
|
383 |
+
if (!$this->hasSetFontUnderline && $baseStyle->isFontUnderline()) {
|
384 |
+
$styleToUpdate->setFontUnderline();
|
385 |
+
}
|
386 |
+
if (!$this->hasSetFontStrikethrough && $baseStyle->isFontStrikethrough()) {
|
387 |
+
$styleToUpdate->setFontStrikethrough();
|
388 |
+
}
|
389 |
+
}
|
390 |
+
|
391 |
+
/**
|
392 |
+
* @param Style $styleToUpdate Style to update (passed as reference)
|
393 |
+
* @param Style $baseStyle
|
394 |
+
* @return void
|
395 |
+
*/
|
396 |
+
private function mergeOtherFontProperties($styleToUpdate, $baseStyle)
|
397 |
+
{
|
398 |
+
if (!$this->hasSetFontSize && $baseStyle->getFontSize() !== self::DEFAULT_FONT_SIZE) {
|
399 |
+
$styleToUpdate->setFontSize($baseStyle->getFontSize());
|
400 |
+
}
|
401 |
+
if (!$this->hasSetFontColor && $baseStyle->getFontColor() !== self::DEFAULT_FONT_COLOR) {
|
402 |
+
$styleToUpdate->setFontColor($baseStyle->getFontColor());
|
403 |
+
}
|
404 |
+
if (!$this->hasSetFontName && $baseStyle->getFontName() !== self::DEFAULT_FONT_NAME) {
|
405 |
+
$styleToUpdate->setFontName($baseStyle->getFontName());
|
406 |
+
}
|
407 |
+
}
|
408 |
+
|
409 |
+
/**
|
410 |
+
* @param Style $styleToUpdate Style to update (passed as reference)
|
411 |
+
* @param Style $baseStyle
|
412 |
+
* @return void
|
413 |
+
*/
|
414 |
+
private function mergeCellProperties($styleToUpdate, $baseStyle)
|
415 |
+
{
|
416 |
+
if (!$this->hasSetWrapText && $baseStyle->shouldWrapText()) {
|
417 |
+
$styleToUpdate->setShouldWrapText();
|
418 |
+
}
|
419 |
+
if (!$this->getBorder() && $baseStyle->shouldApplyBorder()) {
|
420 |
+
$styleToUpdate->setBorder($baseStyle->getBorder());
|
421 |
+
}
|
422 |
+
if (!$this->hasSetBackgroundColor && $baseStyle->shouldApplyBackgroundColor()) {
|
423 |
+
$styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
|
424 |
+
}
|
425 |
+
}
|
426 |
+
}
|
app/Services/Spout/Writer/Style/StyleBuilder.php
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\Style;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Class StyleBuilder
|
7 |
+
* Builder to create new styles
|
8 |
+
*
|
9 |
+
* @package Box\Spout\Writer\Style
|
10 |
+
*/
|
11 |
+
class StyleBuilder
|
12 |
+
{
|
13 |
+
/** @var Style Style to be created */
|
14 |
+
protected $style;
|
15 |
+
|
16 |
+
/**
|
17 |
+
*
|
18 |
+
*/
|
19 |
+
public function __construct()
|
20 |
+
{
|
21 |
+
$this->style = new Style();
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Makes the font bold.
|
26 |
+
*
|
27 |
+
* @api
|
28 |
+
* @return StyleBuilder
|
29 |
+
*/
|
30 |
+
public function setFontBold()
|
31 |
+
{
|
32 |
+
$this->style->setFontBold();
|
33 |
+
return $this;
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Makes the font italic.
|
38 |
+
*
|
39 |
+
* @api
|
40 |
+
* @return StyleBuilder
|
41 |
+
*/
|
42 |
+
public function setFontItalic()
|
43 |
+
{
|
44 |
+
$this->style->setFontItalic();
|
45 |
+
return $this;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Makes the font underlined.
|
50 |
+
*
|
51 |
+
* @api
|
52 |
+
* @return StyleBuilder
|
53 |
+
*/
|
54 |
+
public function setFontUnderline()
|
55 |
+
{
|
56 |
+
$this->style->setFontUnderline();
|
57 |
+
return $this;
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Makes the font struck through.
|
62 |
+
*
|
63 |
+
* @api
|
64 |
+
* @return StyleBuilder
|
65 |
+
*/
|
66 |
+
public function setFontStrikethrough()
|
67 |
+
{
|
68 |
+
$this->style->setFontStrikethrough();
|
69 |
+
return $this;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Sets the font size.
|
74 |
+
*
|
75 |
+
* @api
|
76 |
+
* @param int $fontSize Font size, in pixels
|
77 |
+
* @return StyleBuilder
|
78 |
+
*/
|
79 |
+
public function setFontSize($fontSize)
|
80 |
+
{
|
81 |
+
$this->style->setFontSize($fontSize);
|
82 |
+
return $this;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Sets the font color.
|
87 |
+
*
|
88 |
+
* @api
|
89 |
+
* @param string $fontColor ARGB color (@see Color)
|
90 |
+
* @return StyleBuilder
|
91 |
+
*/
|
92 |
+
public function setFontColor($fontColor)
|
93 |
+
{
|
94 |
+
$this->style->setFontColor($fontColor);
|
95 |
+
return $this;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Sets the font name.
|
100 |
+
*
|
101 |
+
* @api
|
102 |
+
* @param string $fontName Name of the font to use
|
103 |
+
* @return StyleBuilder
|
104 |
+
*/
|
105 |
+
public function setFontName($fontName)
|
106 |
+
{
|
107 |
+
$this->style->setFontName($fontName);
|
108 |
+
return $this;
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Makes the text wrap in the cell if requested
|
113 |
+
*
|
114 |
+
* @api
|
115 |
+
* @param bool $shouldWrap Should the text be wrapped
|
116 |
+
* @return StyleBuilder
|
117 |
+
*/
|
118 |
+
public function setShouldWrapText($shouldWrap = true)
|
119 |
+
{
|
120 |
+
$this->style->setShouldWrapText($shouldWrap);
|
121 |
+
return $this;
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* Set a border
|
126 |
+
*
|
127 |
+
* @param Border $border
|
128 |
+
* @return $this
|
129 |
+
*/
|
130 |
+
public function setBorder(Border $border)
|
131 |
+
{
|
132 |
+
$this->style->setBorder($border);
|
133 |
+
return $this;
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Sets a background color
|
138 |
+
*
|
139 |
+
* @api
|
140 |
+
* @param string $color ARGB color (@see Color)
|
141 |
+
* @return StyleBuilder
|
142 |
+
*/
|
143 |
+
public function setBackgroundColor($color)
|
144 |
+
{
|
145 |
+
$this->style->setBackgroundColor($color);
|
146 |
+
return $this;
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* Returns the configured style. The style is cached and can be reused.
|
151 |
+
*
|
152 |
+
* @api
|
153 |
+
* @return Style
|
154 |
+
*/
|
155 |
+
public function build()
|
156 |
+
{
|
157 |
+
return $this->style;
|
158 |
+
}
|
159 |
+
}
|
app/Services/Spout/Writer/WriterFactory.php
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\UnsupportedTypeException;
|
6 |
+
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
|
7 |
+
use Box\Spout\Common\Type;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class WriterFactory
|
11 |
+
* This factory is used to create writers, based on the type of the file to be read.
|
12 |
+
* It supports CSV, XLSX and ODS formats.
|
13 |
+
*
|
14 |
+
* @package Box\Spout\Writer
|
15 |
+
*/
|
16 |
+
class WriterFactory
|
17 |
+
{
|
18 |
+
/**
|
19 |
+
* This creates an instance of the appropriate writer, given the type of the file to be read
|
20 |
+
*
|
21 |
+
* @api
|
22 |
+
* @param string $writerType Type of the writer to instantiate
|
23 |
+
* @return WriterInterface
|
24 |
+
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
|
25 |
+
*/
|
26 |
+
public static function create($writerType)
|
27 |
+
{
|
28 |
+
$writer = null;
|
29 |
+
|
30 |
+
switch ($writerType) {
|
31 |
+
case Type::CSV:
|
32 |
+
$writer = new CSV\Writer();
|
33 |
+
break;
|
34 |
+
case Type::XLSX:
|
35 |
+
$writer = new XLSX\Writer();
|
36 |
+
break;
|
37 |
+
case Type::ODS:
|
38 |
+
$writer = new ODS\Writer();
|
39 |
+
break;
|
40 |
+
default:
|
41 |
+
throw new UnsupportedTypeException('No writers supporting the given type: ' . $writerType);
|
42 |
+
}
|
43 |
+
|
44 |
+
$writer->setGlobalFunctionsHelper(new GlobalFunctionsHelper());
|
45 |
+
|
46 |
+
return $writer;
|
47 |
+
}
|
48 |
+
}
|
app/Services/Spout/Writer/WriterInterface.php
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface WriterInterface
|
7 |
+
*
|
8 |
+
* @package Box\Spout\Writer
|
9 |
+
*/
|
10 |
+
interface WriterInterface
|
11 |
+
{
|
12 |
+
/**
|
13 |
+
* Inits the writer and opens it to accept data.
|
14 |
+
* By using this method, the data will be written to a file.
|
15 |
+
*
|
16 |
+
* @param string $outputFilePath Path of the output file that will contain the data
|
17 |
+
* @return WriterInterface
|
18 |
+
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
|
19 |
+
*/
|
20 |
+
public function openToFile($outputFilePath);
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Inits the writer and opens it to accept data.
|
24 |
+
* By using this method, the data will be outputted directly to the browser.
|
25 |
+
*
|
26 |
+
* @param string $outputFileName Name of the output file that will contain the data. If a path is passed in, only the file name will be kept
|
27 |
+
* @return WriterInterface
|
28 |
+
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
|
29 |
+
*/
|
30 |
+
public function openToBrowser($outputFileName);
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Write given data to the output. New data will be appended to end of stream.
|
34 |
+
*
|
35 |
+
* @param array $dataRow Array containing data to be streamed.
|
36 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
37 |
+
* @return WriterInterface
|
38 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
39 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
40 |
+
*/
|
41 |
+
public function addRow(array $dataRow);
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Write given data to the output and apply the given style.
|
45 |
+
* @see addRow
|
46 |
+
*
|
47 |
+
* @param array $dataRow Array of array containing data to be streamed.
|
48 |
+
* @param Style\Style $style Style to be applied to the row.
|
49 |
+
* @return WriterInterface
|
50 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
51 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
52 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
53 |
+
*/
|
54 |
+
public function addRowWithStyle(array $dataRow, $style);
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Write given data to the output. New data will be appended to end of stream.
|
58 |
+
*
|
59 |
+
* @param array $dataRows Array of array containing data to be streamed.
|
60 |
+
* Example $dataRow = [
|
61 |
+
* ['data11', 12, , '', 'data13'],
|
62 |
+
* ['data21', 'data22', null],
|
63 |
+
* ];
|
64 |
+
* @return WriterInterface
|
65 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
66 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
|
67 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
68 |
+
*/
|
69 |
+
public function addRows(array $dataRows);
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Write given data to the output and apply the given style.
|
73 |
+
* @see addRows
|
74 |
+
*
|
75 |
+
* @param array $dataRows Array of array containing data to be streamed.
|
76 |
+
* @param Style\Style $style Style to be applied to the rows.
|
77 |
+
* @return WriterInterface
|
78 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
79 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
80 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
81 |
+
*/
|
82 |
+
public function addRowsWithStyle(array $dataRows, $style);
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Closes the writer. This will close the streamer as well, preventing new data
|
86 |
+
* to be written to the file.
|
87 |
+
*
|
88 |
+
* @return void
|
89 |
+
*/
|
90 |
+
public function close();
|
91 |
+
}
|
app/Services/Spout/Writer/XLSX/Helper/BorderHelper.php
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Style\Border;
|
6 |
+
use Box\Spout\Writer\Style\BorderPart;
|
7 |
+
|
8 |
+
class BorderHelper
|
9 |
+
{
|
10 |
+
public static $xlsxStyleMap = [
|
11 |
+
Border::STYLE_SOLID => [
|
12 |
+
Border::WIDTH_THIN => 'thin',
|
13 |
+
Border::WIDTH_MEDIUM => 'medium',
|
14 |
+
Border::WIDTH_THICK => 'thick'
|
15 |
+
],
|
16 |
+
Border::STYLE_DOTTED => [
|
17 |
+
Border::WIDTH_THIN => 'dotted',
|
18 |
+
Border::WIDTH_MEDIUM => 'dotted',
|
19 |
+
Border::WIDTH_THICK => 'dotted',
|
20 |
+
],
|
21 |
+
Border::STYLE_DASHED => [
|
22 |
+
Border::WIDTH_THIN => 'dashed',
|
23 |
+
Border::WIDTH_MEDIUM => 'mediumDashed',
|
24 |
+
Border::WIDTH_THICK => 'mediumDashed',
|
25 |
+
],
|
26 |
+
Border::STYLE_DOUBLE => [
|
27 |
+
Border::WIDTH_THIN => 'double',
|
28 |
+
Border::WIDTH_MEDIUM => 'double',
|
29 |
+
Border::WIDTH_THICK => 'double',
|
30 |
+
],
|
31 |
+
Border::STYLE_NONE => [
|
32 |
+
Border::WIDTH_THIN => 'none',
|
33 |
+
Border::WIDTH_MEDIUM => 'none',
|
34 |
+
Border::WIDTH_THICK => 'none',
|
35 |
+
],
|
36 |
+
];
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @param BorderPart $borderPart
|
40 |
+
* @return string
|
41 |
+
*/
|
42 |
+
public static function serializeBorderPart(BorderPart $borderPart)
|
43 |
+
{
|
44 |
+
$borderStyle = self::getBorderStyle($borderPart);
|
45 |
+
|
46 |
+
$colorEl = $borderPart->getColor() ? sprintf('<color rgb="%s"/>', $borderPart->getColor()) : '';
|
47 |
+
$partEl = sprintf(
|
48 |
+
'<%s style="%s">%s</%s>',
|
49 |
+
$borderPart->getName(),
|
50 |
+
$borderStyle,
|
51 |
+
$colorEl,
|
52 |
+
$borderPart->getName()
|
53 |
+
);
|
54 |
+
|
55 |
+
return $partEl . PHP_EOL;
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Get the style definition from the style map
|
60 |
+
*
|
61 |
+
* @param BorderPart $borderPart
|
62 |
+
* @return string
|
63 |
+
*/
|
64 |
+
protected static function getBorderStyle(BorderPart $borderPart)
|
65 |
+
{
|
66 |
+
return self::$xlsxStyleMap[$borderPart->getStyle()][$borderPart->getWidth()];
|
67 |
+
}
|
68 |
+
}
|
app/Services/Spout/Writer/XLSX/Helper/FileSystemHelper.php
ADDED
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Common\Helper\ZipHelper;
|
6 |
+
use Box\Spout\Writer\XLSX\Internal\Worksheet;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Class FileSystemHelper
|
10 |
+
* This class provides helper functions to help with the file system operations
|
11 |
+
* like files/folders creation & deletion for XLSX files
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Writer\XLSX\Helper
|
14 |
+
*/
|
15 |
+
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper
|
16 |
+
{
|
17 |
+
const APP_NAME = 'Spout';
|
18 |
+
|
19 |
+
const RELS_FOLDER_NAME = '_rels';
|
20 |
+
const DOC_PROPS_FOLDER_NAME = 'docProps';
|
21 |
+
const XL_FOLDER_NAME = 'xl';
|
22 |
+
const WORKSHEETS_FOLDER_NAME = 'worksheets';
|
23 |
+
|
24 |
+
const RELS_FILE_NAME = '.rels';
|
25 |
+
const APP_XML_FILE_NAME = 'app.xml';
|
26 |
+
const CORE_XML_FILE_NAME = 'core.xml';
|
27 |
+
const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml';
|
28 |
+
const WORKBOOK_XML_FILE_NAME = 'workbook.xml';
|
29 |
+
const WORKBOOK_RELS_XML_FILE_NAME = 'workbook.xml.rels';
|
30 |
+
const STYLES_XML_FILE_NAME = 'styles.xml';
|
31 |
+
|
32 |
+
/** @var string Path to the root folder inside the temp folder where the files to create the XLSX will be stored */
|
33 |
+
protected $rootFolder;
|
34 |
+
|
35 |
+
/** @var string Path to the "_rels" folder inside the root folder */
|
36 |
+
protected $relsFolder;
|
37 |
+
|
38 |
+
/** @var string Path to the "docProps" folder inside the root folder */
|
39 |
+
protected $docPropsFolder;
|
40 |
+
|
41 |
+
/** @var string Path to the "xl" folder inside the root folder */
|
42 |
+
protected $xlFolder;
|
43 |
+
|
44 |
+
/** @var string Path to the "_rels" folder inside the "xl" folder */
|
45 |
+
protected $xlRelsFolder;
|
46 |
+
|
47 |
+
/** @var string Path to the "worksheets" folder inside the "xl" folder */
|
48 |
+
protected $xlWorksheetsFolder;
|
49 |
+
|
50 |
+
/**
|
51 |
+
* @return string
|
52 |
+
*/
|
53 |
+
public function getRootFolder()
|
54 |
+
{
|
55 |
+
return $this->rootFolder;
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* @return string
|
60 |
+
*/
|
61 |
+
public function getXlFolder()
|
62 |
+
{
|
63 |
+
return $this->xlFolder;
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* @return string
|
68 |
+
*/
|
69 |
+
public function getXlWorksheetsFolder()
|
70 |
+
{
|
71 |
+
return $this->xlWorksheetsFolder;
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Creates all the folders needed to create a XLSX file, as well as the files that won't change.
|
76 |
+
*
|
77 |
+
* @return void
|
78 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
|
79 |
+
*/
|
80 |
+
public function createBaseFilesAndFolders()
|
81 |
+
{
|
82 |
+
$this
|
83 |
+
->createRootFolder()
|
84 |
+
->createRelsFolderAndFile()
|
85 |
+
->createDocPropsFolderAndFiles()
|
86 |
+
->createXlFolderAndSubFolders();
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Creates the folder that will be used as root
|
91 |
+
*
|
92 |
+
* @return FileSystemHelper
|
93 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
|
94 |
+
*/
|
95 |
+
protected function createRootFolder()
|
96 |
+
{
|
97 |
+
$this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('xlsx', true));
|
98 |
+
return $this;
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Creates the "_rels" folder under the root folder as well as the ".rels" file in it
|
103 |
+
*
|
104 |
+
* @return FileSystemHelper
|
105 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or the ".rels" file
|
106 |
+
*/
|
107 |
+
protected function createRelsFolderAndFile()
|
108 |
+
{
|
109 |
+
$this->relsFolder = $this->createFolder($this->rootFolder, self::RELS_FOLDER_NAME);
|
110 |
+
|
111 |
+
$this->createRelsFile();
|
112 |
+
|
113 |
+
return $this;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Creates the ".rels" file under the "_rels" folder (under root)
|
118 |
+
*
|
119 |
+
* @return FileSystemHelper
|
120 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file
|
121 |
+
*/
|
122 |
+
protected function createRelsFile()
|
123 |
+
{
|
124 |
+
$relsFileContents = <<<EOD
|
125 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
126 |
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
127 |
+
<Relationship Id="rIdWorkbook" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
128 |
+
<Relationship Id="rIdCore" Type="http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
|
129 |
+
<Relationship Id="rIdApp" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
|
130 |
+
</Relationships>
|
131 |
+
EOD;
|
132 |
+
|
133 |
+
$this->createFileWithContents($this->relsFolder, self::RELS_FILE_NAME, $relsFileContents);
|
134 |
+
|
135 |
+
return $this;
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Creates the "docProps" folder under the root folder as well as the "app.xml" and "core.xml" files in it
|
140 |
+
*
|
141 |
+
* @return FileSystemHelper
|
142 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or one of the files
|
143 |
+
*/
|
144 |
+
protected function createDocPropsFolderAndFiles()
|
145 |
+
{
|
146 |
+
$this->docPropsFolder = $this->createFolder($this->rootFolder, self::DOC_PROPS_FOLDER_NAME);
|
147 |
+
|
148 |
+
$this->createAppXmlFile();
|
149 |
+
$this->createCoreXmlFile();
|
150 |
+
|
151 |
+
return $this;
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Creates the "app.xml" file under the "docProps" folder
|
156 |
+
*
|
157 |
+
* @return FileSystemHelper
|
158 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file
|
159 |
+
*/
|
160 |
+
protected function createAppXmlFile()
|
161 |
+
{
|
162 |
+
$appName = self::APP_NAME;
|
163 |
+
$appXmlFileContents = <<<EOD
|
164 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
165 |
+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
|
166 |
+
<Application>$appName</Application>
|
167 |
+
<TotalTime>0</TotalTime>
|
168 |
+
</Properties>
|
169 |
+
EOD;
|
170 |
+
|
171 |
+
$this->createFileWithContents($this->docPropsFolder, self::APP_XML_FILE_NAME, $appXmlFileContents);
|
172 |
+
|
173 |
+
return $this;
|
174 |
+
}
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Creates the "core.xml" file under the "docProps" folder
|
178 |
+
*
|
179 |
+
* @return FileSystemHelper
|
180 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file
|
181 |
+
*/
|
182 |
+
protected function createCoreXmlFile()
|
183 |
+
{
|
184 |
+
$createdDate = (new \DateTime())->format(\DateTime::W3C);
|
185 |
+
$coreXmlFileContents = <<<EOD
|
186 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
187 |
+
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
188 |
+
<dcterms:created xsi:type="dcterms:W3CDTF">$createdDate</dcterms:created>
|
189 |
+
<dcterms:modified xsi:type="dcterms:W3CDTF">$createdDate</dcterms:modified>
|
190 |
+
<cp:revision>0</cp:revision>
|
191 |
+
</cp:coreProperties>
|
192 |
+
EOD;
|
193 |
+
|
194 |
+
$this->createFileWithContents($this->docPropsFolder, self::CORE_XML_FILE_NAME, $coreXmlFileContents);
|
195 |
+
|
196 |
+
return $this;
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Creates the "xl" folder under the root folder as well as its subfolders
|
201 |
+
*
|
202 |
+
* @return FileSystemHelper
|
203 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the folders
|
204 |
+
*/
|
205 |
+
protected function createXlFolderAndSubFolders()
|
206 |
+
{
|
207 |
+
$this->xlFolder = $this->createFolder($this->rootFolder, self::XL_FOLDER_NAME);
|
208 |
+
$this->createXlRelsFolder();
|
209 |
+
$this->createXlWorksheetsFolder();
|
210 |
+
|
211 |
+
return $this;
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Creates the "_rels" folder under the "xl" folder
|
216 |
+
*
|
217 |
+
* @return FileSystemHelper
|
218 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
|
219 |
+
*/
|
220 |
+
protected function createXlRelsFolder()
|
221 |
+
{
|
222 |
+
$this->xlRelsFolder = $this->createFolder($this->xlFolder, self::RELS_FOLDER_NAME);
|
223 |
+
return $this;
|
224 |
+
}
|
225 |
+
|
226 |
+
/**
|
227 |
+
* Creates the "worksheets" folder under the "xl" folder
|
228 |
+
*
|
229 |
+
* @return FileSystemHelper
|
230 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
|
231 |
+
*/
|
232 |
+
protected function createXlWorksheetsFolder()
|
233 |
+
{
|
234 |
+
$this->xlWorksheetsFolder = $this->createFolder($this->xlFolder, self::WORKSHEETS_FOLDER_NAME);
|
235 |
+
return $this;
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Creates the "[Content_Types].xml" file under the root folder
|
240 |
+
*
|
241 |
+
* @param Worksheet[] $worksheets
|
242 |
+
* @return FileSystemHelper
|
243 |
+
*/
|
244 |
+
public function createContentTypesFile($worksheets)
|
245 |
+
{
|
246 |
+
$contentTypesXmlFileContents = <<<EOD
|
247 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
248 |
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
249 |
+
<Default ContentType="application/xml" Extension="xml"/>
|
250 |
+
<Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/>
|
251 |
+
<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/>
|
252 |
+
EOD;
|
253 |
+
|
254 |
+
/** @var Worksheet $worksheet */
|
255 |
+
foreach ($worksheets as $worksheet) {
|
256 |
+
$contentTypesXmlFileContents .= '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet' . $worksheet->getId() . '.xml"/>';
|
257 |
+
}
|
258 |
+
|
259 |
+
$contentTypesXmlFileContents .= <<<EOD
|
260 |
+
<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/>
|
261 |
+
<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/>
|
262 |
+
<Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/>
|
263 |
+
<Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/>
|
264 |
+
</Types>
|
265 |
+
EOD;
|
266 |
+
|
267 |
+
$this->createFileWithContents($this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME, $contentTypesXmlFileContents);
|
268 |
+
|
269 |
+
return $this;
|
270 |
+
}
|
271 |
+
|
272 |
+
/**
|
273 |
+
* Creates the "workbook.xml" file under the "xl" folder
|
274 |
+
*
|
275 |
+
* @param Worksheet[] $worksheets
|
276 |
+
* @return FileSystemHelper
|
277 |
+
*/
|
278 |
+
public function createWorkbookFile($worksheets)
|
279 |
+
{
|
280 |
+
$workbookXmlFileContents = <<<EOD
|
281 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
282 |
+
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
283 |
+
<sheets>
|
284 |
+
EOD;
|
285 |
+
|
286 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
287 |
+
$escaper = \Box\Spout\Common\Escaper\XLSX::getInstance();
|
288 |
+
|
289 |
+
/** @var Worksheet $worksheet */
|
290 |
+
foreach ($worksheets as $worksheet) {
|
291 |
+
$worksheetName = $worksheet->getExternalSheet()->getName();
|
292 |
+
$worksheetId = $worksheet->getId();
|
293 |
+
$workbookXmlFileContents .= '<sheet name="' . $escaper->escape($worksheetName) . '" sheetId="' . $worksheetId . '" r:id="rIdSheet' . $worksheetId . '"/>';
|
294 |
+
}
|
295 |
+
|
296 |
+
$workbookXmlFileContents .= <<<EOD
|
297 |
+
</sheets>
|
298 |
+
</workbook>
|
299 |
+
EOD;
|
300 |
+
|
301 |
+
$this->createFileWithContents($this->xlFolder, self::WORKBOOK_XML_FILE_NAME, $workbookXmlFileContents);
|
302 |
+
|
303 |
+
return $this;
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Creates the "workbook.xml.res" file under the "xl/_res" folder
|
308 |
+
*
|
309 |
+
* @param Worksheet[] $worksheets
|
310 |
+
* @return FileSystemHelper
|
311 |
+
*/
|
312 |
+
public function createWorkbookRelsFile($worksheets)
|
313 |
+
{
|
314 |
+
$workbookRelsXmlFileContents = <<<EOD
|
315 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
316 |
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
317 |
+
<Relationship Id="rIdStyles" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/>
|
318 |
+
<Relationship Id="rIdSharedStrings" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/>
|
319 |
+
EOD;
|
320 |
+
|
321 |
+
/** @var Worksheet $worksheet */
|
322 |
+
foreach ($worksheets as $worksheet) {
|
323 |
+
$worksheetId = $worksheet->getId();
|
324 |
+
$workbookRelsXmlFileContents .= '<Relationship Id="rIdSheet' . $worksheetId . '" Target="worksheets/sheet' . $worksheetId . '.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/>';
|
325 |
+
}
|
326 |
+
|
327 |
+
$workbookRelsXmlFileContents .= '</Relationships>';
|
328 |
+
|
329 |
+
$this->createFileWithContents($this->xlRelsFolder, self::WORKBOOK_RELS_XML_FILE_NAME, $workbookRelsXmlFileContents);
|
330 |
+
|
331 |
+
return $this;
|
332 |
+
}
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Creates the "styles.xml" file under the "xl" folder
|
336 |
+
*
|
337 |
+
* @param StyleHelper $styleHelper
|
338 |
+
* @return FileSystemHelper
|
339 |
+
*/
|
340 |
+
public function createStylesFile($styleHelper)
|
341 |
+
{
|
342 |
+
$stylesXmlFileContents = $styleHelper->getStylesXMLFileContent();
|
343 |
+
$this->createFileWithContents($this->xlFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents);
|
344 |
+
|
345 |
+
return $this;
|
346 |
+
}
|
347 |
+
|
348 |
+
/**
|
349 |
+
* Zips the root folder and streams the contents of the zip into the given stream
|
350 |
+
*
|
351 |
+
* @param resource $streamPointer Pointer to the stream to copy the zip
|
352 |
+
* @return void
|
353 |
+
*/
|
354 |
+
public function zipRootFolderAndCopyToStream($streamPointer)
|
355 |
+
{
|
356 |
+
$zipHelper = new ZipHelper($this->rootFolder);
|
357 |
+
|
358 |
+
// In order to have the file's mime type detected properly, files need to be added
|
359 |
+
// to the zip file in a particular order.
|
360 |
+
// "[Content_Types].xml" then at least 2 files located in "xl" folder should be zipped first.
|
361 |
+
$zipHelper->addFileToArchive($this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME);
|
362 |
+
$zipHelper->addFileToArchive($this->rootFolder, self::XL_FOLDER_NAME . '/' . self::WORKBOOK_XML_FILE_NAME);
|
363 |
+
$zipHelper->addFileToArchive($this->rootFolder, self::XL_FOLDER_NAME . '/' . self::STYLES_XML_FILE_NAME);
|
364 |
+
|
365 |
+
$zipHelper->addFolderToArchive($this->rootFolder, ZipHelper::EXISTING_FILES_SKIP);
|
366 |
+
$zipHelper->closeArchiveAndCopyToStream($streamPointer);
|
367 |
+
|
368 |
+
// once the zip is copied, remove it
|
369 |
+
$this->deleteFile($zipHelper->getZipFilePath());
|
370 |
+
}
|
371 |
+
}
|
app/Services/Spout/Writer/XLSX/Helper/SharedStringsHelper.php
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\IOException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Class SharedStringsHelper
|
9 |
+
* This class provides helper functions to write shared strings
|
10 |
+
*
|
11 |
+
* @package Box\Spout\Writer\XLSX\Helper
|
12 |
+
*/
|
13 |
+
class SharedStringsHelper
|
14 |
+
{
|
15 |
+
const SHARED_STRINGS_FILE_NAME = 'sharedStrings.xml';
|
16 |
+
|
17 |
+
const SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER = <<<EOD
|
18 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
19 |
+
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
20 |
+
EOD;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* This number must be really big so that the no generated file will have more strings than that.
|
24 |
+
* If the strings number goes above, characters will be overwritten in an unwanted way and will corrupt the file.
|
25 |
+
*/
|
26 |
+
const DEFAULT_STRINGS_COUNT_PART = 'count="9999999999999" uniqueCount="9999999999999"';
|
27 |
+
|
28 |
+
/** @var resource Pointer to the sharedStrings.xml file */
|
29 |
+
protected $sharedStringsFilePointer;
|
30 |
+
|
31 |
+
/** @var int Number of shared strings already written */
|
32 |
+
protected $numSharedStrings = 0;
|
33 |
+
|
34 |
+
/** @var \Box\Spout\Common\Escaper\XLSX Strings escaper */
|
35 |
+
protected $stringsEscaper;
|
36 |
+
|
37 |
+
/**
|
38 |
+
* @param string $xlFolder Path to the "xl" folder
|
39 |
+
*/
|
40 |
+
public function __construct($xlFolder)
|
41 |
+
{
|
42 |
+
$sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME;
|
43 |
+
$this->sharedStringsFilePointer = fopen($sharedStringsFilePath, 'w');
|
44 |
+
|
45 |
+
$this->throwIfSharedStringsFilePointerIsNotAvailable();
|
46 |
+
|
47 |
+
// the headers is split into different parts so that we can fseek and put in the correct count and uniqueCount later
|
48 |
+
$header = self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER . ' ' . self::DEFAULT_STRINGS_COUNT_PART . '>';
|
49 |
+
fwrite($this->sharedStringsFilePointer, $header);
|
50 |
+
|
51 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
52 |
+
$this->stringsEscaper = \Box\Spout\Common\Escaper\XLSX::getInstance();
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Checks if the book has been created. Throws an exception if not created yet.
|
57 |
+
*
|
58 |
+
* @return void
|
59 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
60 |
+
*/
|
61 |
+
protected function throwIfSharedStringsFilePointerIsNotAvailable()
|
62 |
+
{
|
63 |
+
if (!$this->sharedStringsFilePointer) {
|
64 |
+
throw new IOException('Unable to open shared strings file for writing.');
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Writes the given string into the sharedStrings.xml file.
|
70 |
+
* Starting and ending whitespaces are preserved.
|
71 |
+
*
|
72 |
+
* @param string $string
|
73 |
+
* @return int ID of the written shared string
|
74 |
+
*/
|
75 |
+
public function writeString($string)
|
76 |
+
{
|
77 |
+
fwrite($this->sharedStringsFilePointer, '<si><t xml:space="preserve">' . $this->stringsEscaper->escape($string) . '</t></si>');
|
78 |
+
$this->numSharedStrings++;
|
79 |
+
|
80 |
+
// Shared string ID is zero-based
|
81 |
+
return ($this->numSharedStrings - 1);
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Finishes writing the data in the sharedStrings.xml file and closes the file.
|
86 |
+
*
|
87 |
+
* @return void
|
88 |
+
*/
|
89 |
+
public function close()
|
90 |
+
{
|
91 |
+
if (!is_resource($this->sharedStringsFilePointer)) {
|
92 |
+
return;
|
93 |
+
}
|
94 |
+
|
95 |
+
fwrite($this->sharedStringsFilePointer, '</sst>');
|
96 |
+
|
97 |
+
// Replace the default strings count with the actual number of shared strings in the file header
|
98 |
+
$firstPartHeaderLength = strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER);
|
99 |
+
$defaultStringsCountPartLength = strlen(self::DEFAULT_STRINGS_COUNT_PART);
|
100 |
+
|
101 |
+
// Adding 1 to take into account the space between the last xml attribute and "count"
|
102 |
+
fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1);
|
103 |
+
fwrite($this->sharedStringsFilePointer, sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"'));
|
104 |
+
|
105 |
+
fclose($this->sharedStringsFilePointer);
|
106 |
+
}
|
107 |
+
}
|
app/Services/Spout/Writer/XLSX/Helper/StyleHelper.php
ADDED
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX\Helper;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Common\Helper\AbstractStyleHelper;
|
6 |
+
use Box\Spout\Writer\Style\Color;
|
7 |
+
use Box\Spout\Writer\Style\Style;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class StyleHelper
|
11 |
+
* This class provides helper functions to manage styles
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Writer\XLSX\Helper
|
14 |
+
*/
|
15 |
+
class StyleHelper extends AbstractStyleHelper
|
16 |
+
{
|
17 |
+
/**
|
18 |
+
* @var array
|
19 |
+
*/
|
20 |
+
protected $registeredFills = [];
|
21 |
+
|
22 |
+
/**
|
23 |
+
* @var array [STYLE_ID] => [FILL_ID] maps a style to a fill declaration
|
24 |
+
*/
|
25 |
+
protected $styleIdToFillMappingTable = [];
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Excel preserves two default fills with index 0 and 1
|
29 |
+
* Since Excel is the dominant vendor - we play along here
|
30 |
+
*
|
31 |
+
* @var int The fill index counter for custom fills.
|
32 |
+
*/
|
33 |
+
protected $fillIndex = 2;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @var array
|
37 |
+
*/
|
38 |
+
protected $registeredBorders = [];
|
39 |
+
|
40 |
+
/**
|
41 |
+
* @var array [STYLE_ID] => [BORDER_ID] maps a style to a border declaration
|
42 |
+
*/
|
43 |
+
protected $styleIdToBorderMappingTable = [];
|
44 |
+
|
45 |
+
/**
|
46 |
+
* XLSX specific operations on the registered styles
|
47 |
+
*
|
48 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
49 |
+
* @return \Box\Spout\Writer\Style\Style
|
50 |
+
*/
|
51 |
+
public function registerStyle($style)
|
52 |
+
{
|
53 |
+
$registeredStyle = parent::registerStyle($style);
|
54 |
+
$this->registerFill($registeredStyle);
|
55 |
+
$this->registerBorder($registeredStyle);
|
56 |
+
return $registeredStyle;
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Register a fill definition
|
61 |
+
*
|
62 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
63 |
+
*/
|
64 |
+
protected function registerFill($style)
|
65 |
+
{
|
66 |
+
$styleId = $style->getId();
|
67 |
+
|
68 |
+
// Currently - only solid backgrounds are supported
|
69 |
+
// so $backgroundColor is a scalar value (RGB Color)
|
70 |
+
$backgroundColor = $style->getBackgroundColor();
|
71 |
+
|
72 |
+
if ($backgroundColor) {
|
73 |
+
$isBackgroundColorRegistered = isset($this->registeredFills[$backgroundColor]);
|
74 |
+
|
75 |
+
// We need to track the already registered background definitions
|
76 |
+
if ($isBackgroundColorRegistered) {
|
77 |
+
$registeredStyleId = $this->registeredFills[$backgroundColor];
|
78 |
+
$registeredFillId = $this->styleIdToFillMappingTable[$registeredStyleId];
|
79 |
+
$this->styleIdToFillMappingTable[$styleId] = $registeredFillId;
|
80 |
+
} else {
|
81 |
+
$this->registeredFills[$backgroundColor] = $styleId;
|
82 |
+
$this->styleIdToFillMappingTable[$styleId] = $this->fillIndex++;
|
83 |
+
}
|
84 |
+
|
85 |
+
} else {
|
86 |
+
// The fillId maps a style to a fill declaration
|
87 |
+
// When there is no background color definition - we default to 0
|
88 |
+
$this->styleIdToFillMappingTable[$styleId] = 0;
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Register a border definition
|
94 |
+
*
|
95 |
+
* @param \Box\Spout\Writer\Style\Style $style
|
96 |
+
*/
|
97 |
+
protected function registerBorder($style)
|
98 |
+
{
|
99 |
+
$styleId = $style->getId();
|
100 |
+
|
101 |
+
if ($style->shouldApplyBorder()) {
|
102 |
+
$border = $style->getBorder();
|
103 |
+
$serializedBorder = serialize($border);
|
104 |
+
|
105 |
+
$isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]);
|
106 |
+
|
107 |
+
if ($isBorderAlreadyRegistered) {
|
108 |
+
$registeredStyleId = $this->registeredBorders[$serializedBorder];
|
109 |
+
$registeredBorderId = $this->styleIdToBorderMappingTable[$registeredStyleId];
|
110 |
+
$this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId;
|
111 |
+
} else {
|
112 |
+
$this->registeredBorders[$serializedBorder] = $styleId;
|
113 |
+
$this->styleIdToBorderMappingTable[$styleId] = count($this->registeredBorders);
|
114 |
+
}
|
115 |
+
|
116 |
+
} else {
|
117 |
+
// If no border should be applied - the mapping is the default border: 0
|
118 |
+
$this->styleIdToBorderMappingTable[$styleId] = 0;
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
|
123 |
+
/**
|
124 |
+
* For empty cells, we can specify a style or not. If no style are specified,
|
125 |
+
* then the software default will be applied. But sometimes, it may be useful
|
126 |
+
* to override this default style, for instance if the cell should have a
|
127 |
+
* background color different than the default one or some borders
|
128 |
+
* (fonts property don't really matter here).
|
129 |
+
*
|
130 |
+
* @param int $styleId
|
131 |
+
* @return bool Whether the cell should define a custom style
|
132 |
+
*/
|
133 |
+
public function shouldApplyStyleOnEmptyCell($styleId)
|
134 |
+
{
|
135 |
+
$hasStyleCustomFill = (isset($this->styleIdToFillMappingTable[$styleId]) && $this->styleIdToFillMappingTable[$styleId] !== 0);
|
136 |
+
$hasStyleCustomBorders = (isset($this->styleIdToBorderMappingTable[$styleId]) && $this->styleIdToBorderMappingTable[$styleId] !== 0);
|
137 |
+
|
138 |
+
return ($hasStyleCustomFill || $hasStyleCustomBorders);
|
139 |
+
}
|
140 |
+
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Returns the content of the "styles.xml" file, given a list of styles.
|
144 |
+
*
|
145 |
+
* @return string
|
146 |
+
*/
|
147 |
+
public function getStylesXMLFileContent()
|
148 |
+
{
|
149 |
+
$content = <<<EOD
|
150 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
151 |
+
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
152 |
+
EOD;
|
153 |
+
|
154 |
+
$content .= $this->getFontsSectionContent();
|
155 |
+
$content .= $this->getFillsSectionContent();
|
156 |
+
$content .= $this->getBordersSectionContent();
|
157 |
+
$content .= $this->getCellStyleXfsSectionContent();
|
158 |
+
$content .= $this->getCellXfsSectionContent();
|
159 |
+
$content .= $this->getCellStylesSectionContent();
|
160 |
+
|
161 |
+
$content .= <<<EOD
|
162 |
+
</styleSheet>
|
163 |
+
EOD;
|
164 |
+
|
165 |
+
return $content;
|
166 |
+
}
|
167 |
+
|
168 |
+
/**
|
169 |
+
* Returns the content of the "<fonts>" section.
|
170 |
+
*
|
171 |
+
* @return string
|
172 |
+
*/
|
173 |
+
protected function getFontsSectionContent()
|
174 |
+
{
|
175 |
+
$content = '<fonts count="' . count($this->styleIdToStyleMappingTable) . '">';
|
176 |
+
|
177 |
+
/** @var \Box\Spout\Writer\Style\Style $style */
|
178 |
+
foreach ($this->getRegisteredStyles() as $style) {
|
179 |
+
$content .= '<font>';
|
180 |
+
|
181 |
+
$content .= '<sz val="' . $style->getFontSize() . '"/>';
|
182 |
+
$content .= '<color rgb="' . Color::toARGB($style->getFontColor()) . '"/>';
|
183 |
+
$content .= '<name val="' . $style->getFontName() . '"/>';
|
184 |
+
|
185 |
+
if ($style->isFontBold()) {
|
186 |
+
$content .= '<b/>';
|
187 |
+
}
|
188 |
+
if ($style->isFontItalic()) {
|
189 |
+
$content .= '<i/>';
|
190 |
+
}
|
191 |
+
if ($style->isFontUnderline()) {
|
192 |
+
$content .= '<u/>';
|
193 |
+
}
|
194 |
+
if ($style->isFontStrikethrough()) {
|
195 |
+
$content .= '<strike/>';
|
196 |
+
}
|
197 |
+
|
198 |
+
$content .= '</font>';
|
199 |
+
}
|
200 |
+
|
201 |
+
$content .= '</fonts>';
|
202 |
+
|
203 |
+
return $content;
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Returns the content of the "<fills>" section.
|
208 |
+
*
|
209 |
+
* @return string
|
210 |
+
*/
|
211 |
+
protected function getFillsSectionContent()
|
212 |
+
{
|
213 |
+
// Excel reserves two default fills
|
214 |
+
$fillsCount = count($this->registeredFills) + 2;
|
215 |
+
$content = sprintf('<fills count="%d">', $fillsCount);
|
216 |
+
|
217 |
+
$content .= '<fill><patternFill patternType="none"/></fill>';
|
218 |
+
$content .= '<fill><patternFill patternType="gray125"/></fill>';
|
219 |
+
|
220 |
+
// The other fills are actually registered by setting a background color
|
221 |
+
foreach ($this->registeredFills as $styleId) {
|
222 |
+
/** @var Style $style */
|
223 |
+
$style = $this->styleIdToStyleMappingTable[$styleId];
|
224 |
+
|
225 |
+
$backgroundColor = $style->getBackgroundColor();
|
226 |
+
$content .= sprintf(
|
227 |
+
'<fill><patternFill patternType="solid"><fgColor rgb="%s"/></patternFill></fill>',
|
228 |
+
$backgroundColor
|
229 |
+
);
|
230 |
+
}
|
231 |
+
|
232 |
+
$content .= '</fills>';
|
233 |
+
|
234 |
+
return $content;
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Returns the content of the "<borders>" section.
|
239 |
+
*
|
240 |
+
* @return string
|
241 |
+
*/
|
242 |
+
protected function getBordersSectionContent()
|
243 |
+
{
|
244 |
+
|
245 |
+
// There is one default border with index 0
|
246 |
+
$borderCount = count($this->registeredBorders) + 1;
|
247 |
+
|
248 |
+
$content = '<borders count="' . $borderCount . '">';
|
249 |
+
|
250 |
+
// Default border starting at index 0
|
251 |
+
$content .= '<border><left/><right/><top/><bottom/></border>';
|
252 |
+
|
253 |
+
foreach ($this->registeredBorders as $styleId) {
|
254 |
+
/** @var \Box\Spout\Writer\Style\Style $style */
|
255 |
+
$style = $this->styleIdToStyleMappingTable[$styleId];
|
256 |
+
$border = $style->getBorder();
|
257 |
+
$content .= '<border>';
|
258 |
+
|
259 |
+
// @link https://github.com/box/spout/issues/271
|
260 |
+
$sortOrder = ['left', 'right', 'top', 'bottom'];
|
261 |
+
|
262 |
+
foreach ($sortOrder as $partName) {
|
263 |
+
if ($border->hasPart($partName)) {
|
264 |
+
/** @var $part \Box\Spout\Writer\Style\BorderPart */
|
265 |
+
$part = $border->getPart($partName);
|
266 |
+
$content .= BorderHelper::serializeBorderPart($part);
|
267 |
+
}
|
268 |
+
|
269 |
+
}
|
270 |
+
|
271 |
+
$content .= '</border>';
|
272 |
+
}
|
273 |
+
|
274 |
+
$content .= '</borders>';
|
275 |
+
|
276 |
+
return $content;
|
277 |
+
}
|
278 |
+
|
279 |
+
/**
|
280 |
+
* Returns the content of the "<cellStyleXfs>" section.
|
281 |
+
*
|
282 |
+
* @return string
|
283 |
+
*/
|
284 |
+
protected function getCellStyleXfsSectionContent()
|
285 |
+
{
|
286 |
+
return <<<EOD
|
287 |
+
<cellStyleXfs count="1">
|
288 |
+
<xf borderId="0" fillId="0" fontId="0" numFmtId="0"/>
|
289 |
+
</cellStyleXfs>
|
290 |
+
EOD;
|
291 |
+
}
|
292 |
+
|
293 |
+
/**
|
294 |
+
* Returns the content of the "<cellXfs>" section.
|
295 |
+
*
|
296 |
+
* @return string
|
297 |
+
*/
|
298 |
+
protected function getCellXfsSectionContent()
|
299 |
+
{
|
300 |
+
$registeredStyles = $this->getRegisteredStyles();
|
301 |
+
|
302 |
+
$content = '<cellXfs count="' . count($registeredStyles) . '">';
|
303 |
+
|
304 |
+
foreach ($registeredStyles as $style) {
|
305 |
+
$styleId = $style->getId();
|
306 |
+
$fillId = $this->styleIdToFillMappingTable[$styleId];
|
307 |
+
$borderId = $this->styleIdToBorderMappingTable[$styleId];
|
308 |
+
|
309 |
+
$content .= '<xf numFmtId="0" fontId="' . $styleId . '" fillId="' . $fillId . '" borderId="' . $borderId . '" xfId="0"';
|
310 |
+
|
311 |
+
if ($style->shouldApplyFont()) {
|
312 |
+
$content .= ' applyFont="1"';
|
313 |
+
}
|
314 |
+
|
315 |
+
$content .= sprintf(' applyBorder="%d"', $style->shouldApplyBorder() ? 1 : 0);
|
316 |
+
|
317 |
+
if ($style->shouldWrapText()) {
|
318 |
+
$content .= ' applyAlignment="1">';
|
319 |
+
$content .= '<alignment wrapText="1"/>';
|
320 |
+
$content .= '</xf>';
|
321 |
+
} else {
|
322 |
+
$content .= '/>';
|
323 |
+
}
|
324 |
+
}
|
325 |
+
|
326 |
+
$content .= '</cellXfs>';
|
327 |
+
|
328 |
+
return $content;
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Returns the content of the "<cellStyles>" section.
|
333 |
+
*
|
334 |
+
* @return string
|
335 |
+
*/
|
336 |
+
protected function getCellStylesSectionContent()
|
337 |
+
{
|
338 |
+
return <<<EOD
|
339 |
+
<cellStyles count="1">
|
340 |
+
<cellStyle builtinId="0" name="Normal" xfId="0"/>
|
341 |
+
</cellStyles>
|
342 |
+
EOD;
|
343 |
+
}
|
344 |
+
}
|
app/Services/Spout/Writer/XLSX/Internal/Workbook.php
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX\Internal;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\Common\Internal\AbstractWorkbook;
|
6 |
+
use Box\Spout\Writer\XLSX\Helper\FileSystemHelper;
|
7 |
+
use Box\Spout\Writer\XLSX\Helper\SharedStringsHelper;
|
8 |
+
use Box\Spout\Writer\XLSX\Helper\StyleHelper;
|
9 |
+
use Box\Spout\Writer\Common\Sheet;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Class Workbook
|
13 |
+
* Represents a workbook within a XLSX file.
|
14 |
+
* It provides the functions to work with worksheets.
|
15 |
+
*
|
16 |
+
* @package Box\Spout\Writer\XLSX\Internal
|
17 |
+
*/
|
18 |
+
class Workbook extends AbstractWorkbook
|
19 |
+
{
|
20 |
+
/**
|
21 |
+
* Maximum number of rows a XLSX sheet can contain
|
22 |
+
* @see http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP010073849.aspx
|
23 |
+
*/
|
24 |
+
protected static $maxRowsPerWorksheet = 1048576;
|
25 |
+
|
26 |
+
/** @var bool Whether inline or shared strings should be used */
|
27 |
+
protected $shouldUseInlineStrings;
|
28 |
+
|
29 |
+
/** @var \Box\Spout\Writer\XLSX\Helper\FileSystemHelper Helper to perform file system operations */
|
30 |
+
protected $fileSystemHelper;
|
31 |
+
|
32 |
+
/** @var \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper Helper to write shared strings */
|
33 |
+
protected $sharedStringsHelper;
|
34 |
+
|
35 |
+
/** @var \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to apply styles */
|
36 |
+
protected $styleHelper;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @param string $tempFolder
|
40 |
+
* @param bool $shouldUseInlineStrings
|
41 |
+
* @param bool $shouldCreateNewSheetsAutomatically
|
42 |
+
* @param \Box\Spout\Writer\Style\Style $defaultRowStyle
|
43 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
|
44 |
+
*/
|
45 |
+
public function __construct($tempFolder, $shouldUseInlineStrings, $shouldCreateNewSheetsAutomatically, $defaultRowStyle)
|
46 |
+
{
|
47 |
+
parent::__construct($shouldCreateNewSheetsAutomatically, $defaultRowStyle);
|
48 |
+
|
49 |
+
$this->shouldUseInlineStrings = $shouldUseInlineStrings;
|
50 |
+
|
51 |
+
$this->fileSystemHelper = new FileSystemHelper($tempFolder);
|
52 |
+
$this->fileSystemHelper->createBaseFilesAndFolders();
|
53 |
+
|
54 |
+
$this->styleHelper = new StyleHelper($defaultRowStyle);
|
55 |
+
|
56 |
+
// This helper will be shared by all sheets
|
57 |
+
$xlFolder = $this->fileSystemHelper->getXlFolder();
|
58 |
+
$this->sharedStringsHelper = new SharedStringsHelper($xlFolder);
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @return \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to apply styles to XLSX files
|
63 |
+
*/
|
64 |
+
protected function getStyleHelper()
|
65 |
+
{
|
66 |
+
return $this->styleHelper;
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* @return int Maximum number of rows/columns a sheet can contain
|
71 |
+
*/
|
72 |
+
protected function getMaxRowsPerWorksheet()
|
73 |
+
{
|
74 |
+
return self::$maxRowsPerWorksheet;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Creates a new sheet in the workbook. The current sheet remains unchanged.
|
79 |
+
*
|
80 |
+
* @return Worksheet The created sheet
|
81 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the sheet for writing
|
82 |
+
*/
|
83 |
+
public function addNewSheet()
|
84 |
+
{
|
85 |
+
$newSheetIndex = count($this->worksheets);
|
86 |
+
$sheet = new Sheet($newSheetIndex, $this->internalId);
|
87 |
+
|
88 |
+
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
|
89 |
+
$worksheet = new Worksheet($sheet, $worksheetFilesFolder, $this->sharedStringsHelper, $this->styleHelper, $this->shouldUseInlineStrings);
|
90 |
+
$this->worksheets[] = $worksheet;
|
91 |
+
|
92 |
+
return $worksheet;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Closes the workbook and all its associated sheets.
|
97 |
+
* All the necessary files are written to disk and zipped together to create the XLSX file.
|
98 |
+
* All the temporary files are then deleted.
|
99 |
+
*
|
100 |
+
* @param resource $finalFilePointer Pointer to the XLSX that will be created
|
101 |
+
* @return void
|
102 |
+
*/
|
103 |
+
public function close($finalFilePointer)
|
104 |
+
{
|
105 |
+
/** @var Worksheet[] $worksheets */
|
106 |
+
$worksheets = $this->worksheets;
|
107 |
+
|
108 |
+
foreach ($worksheets as $worksheet) {
|
109 |
+
$worksheet->close();
|
110 |
+
}
|
111 |
+
|
112 |
+
$this->sharedStringsHelper->close();
|
113 |
+
|
114 |
+
// Finish creating all the necessary files before zipping everything together
|
115 |
+
$this->fileSystemHelper
|
116 |
+
->createContentTypesFile($worksheets)
|
117 |
+
->createWorkbookFile($worksheets)
|
118 |
+
->createWorkbookRelsFile($worksheets)
|
119 |
+
->createStylesFile($this->styleHelper)
|
120 |
+
->zipRootFolderAndCopyToStream($finalFilePointer);
|
121 |
+
|
122 |
+
$this->cleanupTempFolder();
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Deletes the root folder created in the temp folder and all its contents.
|
127 |
+
*
|
128 |
+
* @return void
|
129 |
+
*/
|
130 |
+
protected function cleanupTempFolder()
|
131 |
+
{
|
132 |
+
$xlsxRootFolder = $this->fileSystemHelper->getRootFolder();
|
133 |
+
$this->fileSystemHelper->deleteFolderRecursively($xlsxRootFolder);
|
134 |
+
}
|
135 |
+
}
|
app/Services/Spout/Writer/XLSX/Internal/Worksheet.php
ADDED
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX\Internal;
|
4 |
+
|
5 |
+
use Box\Spout\Common\Exception\InvalidArgumentException;
|
6 |
+
use Box\Spout\Common\Exception\IOException;
|
7 |
+
use Box\Spout\Common\Helper\StringHelper;
|
8 |
+
use Box\Spout\Writer\Common\Helper\CellHelper;
|
9 |
+
use Box\Spout\Writer\Common\Internal\WorksheetInterface;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Class Worksheet
|
13 |
+
* Represents a worksheet within a XLSX file. The difference with the Sheet object is
|
14 |
+
* that this class provides an interface to write data
|
15 |
+
*
|
16 |
+
* @package Box\Spout\Writer\XLSX\Internal
|
17 |
+
*/
|
18 |
+
class Worksheet implements WorksheetInterface
|
19 |
+
{
|
20 |
+
/**
|
21 |
+
* Maximum number of characters a cell can contain
|
22 |
+
* @see https://support.office.com/en-us/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa [Excel 2007]
|
23 |
+
* @see https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 [Excel 2010]
|
24 |
+
* @see https://support.office.com/en-us/article/Excel-specifications-and-limits-ca36e2dc-1f09-4620-b726-67c00b05040f [Excel 2013/2016]
|
25 |
+
*/
|
26 |
+
const MAX_CHARACTERS_PER_CELL = 32767;
|
27 |
+
|
28 |
+
const SHEET_XML_FILE_HEADER = <<<EOD
|
29 |
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
30 |
+
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
31 |
+
EOD;
|
32 |
+
|
33 |
+
/** @var \Box\Spout\Writer\Common\Sheet The "external" sheet */
|
34 |
+
protected $externalSheet;
|
35 |
+
|
36 |
+
/** @var string Path to the XML file that will contain the sheet data */
|
37 |
+
protected $worksheetFilePath;
|
38 |
+
|
39 |
+
/** @var \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper Helper to write shared strings */
|
40 |
+
protected $sharedStringsHelper;
|
41 |
+
|
42 |
+
/** @var \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to work with styles */
|
43 |
+
protected $styleHelper;
|
44 |
+
|
45 |
+
/** @var bool Whether inline or shared strings should be used */
|
46 |
+
protected $shouldUseInlineStrings;
|
47 |
+
|
48 |
+
/** @var \Box\Spout\Common\Escaper\XLSX Strings escaper */
|
49 |
+
protected $stringsEscaper;
|
50 |
+
|
51 |
+
/** @var \Box\Spout\Common\Helper\StringHelper String helper */
|
52 |
+
protected $stringHelper;
|
53 |
+
|
54 |
+
/** @var Resource Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */
|
55 |
+
protected $sheetFilePointer;
|
56 |
+
|
57 |
+
/** @var int Index of the last written row */
|
58 |
+
protected $lastWrittenRowIndex = 0;
|
59 |
+
|
60 |
+
/**
|
61 |
+
* @param \Box\Spout\Writer\Common\Sheet $externalSheet The associated "external" sheet
|
62 |
+
* @param string $worksheetFilesFolder Temporary folder where the files to create the XLSX will be stored
|
63 |
+
* @param \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper $sharedStringsHelper Helper for shared strings
|
64 |
+
* @param \Box\Spout\Writer\XLSX\Helper\StyleHelper Helper to work with styles
|
65 |
+
* @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
|
66 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
67 |
+
*/
|
68 |
+
public function __construct($externalSheet, $worksheetFilesFolder, $sharedStringsHelper, $styleHelper, $shouldUseInlineStrings)
|
69 |
+
{
|
70 |
+
$this->externalSheet = $externalSheet;
|
71 |
+
$this->sharedStringsHelper = $sharedStringsHelper;
|
72 |
+
$this->styleHelper = $styleHelper;
|
73 |
+
$this->shouldUseInlineStrings = $shouldUseInlineStrings;
|
74 |
+
|
75 |
+
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
76 |
+
$this->stringsEscaper = \Box\Spout\Common\Escaper\XLSX::getInstance();
|
77 |
+
$this->stringHelper = new StringHelper();
|
78 |
+
|
79 |
+
$this->worksheetFilePath = $worksheetFilesFolder . '/' . strtolower($this->externalSheet->getName()) . '.xml';
|
80 |
+
$this->startSheet();
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Prepares the worksheet to accept data
|
85 |
+
*
|
86 |
+
* @return void
|
87 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
88 |
+
*/
|
89 |
+
protected function startSheet()
|
90 |
+
{
|
91 |
+
$this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
|
92 |
+
$this->throwIfSheetFilePointerIsNotAvailable();
|
93 |
+
|
94 |
+
fwrite($this->sheetFilePointer, self::SHEET_XML_FILE_HEADER);
|
95 |
+
fwrite($this->sheetFilePointer, '<sheetData>');
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Checks if the book has been created. Throws an exception if not created yet.
|
100 |
+
*
|
101 |
+
* @return void
|
102 |
+
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
103 |
+
*/
|
104 |
+
protected function throwIfSheetFilePointerIsNotAvailable()
|
105 |
+
{
|
106 |
+
if (!$this->sheetFilePointer) {
|
107 |
+
throw new IOException('Unable to open sheet for writing.');
|
108 |
+
}
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* @return \Box\Spout\Writer\Common\Sheet The "external" sheet
|
113 |
+
*/
|
114 |
+
public function getExternalSheet()
|
115 |
+
{
|
116 |
+
return $this->externalSheet;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* @return int The index of the last written row
|
121 |
+
*/
|
122 |
+
public function getLastWrittenRowIndex()
|
123 |
+
{
|
124 |
+
return $this->lastWrittenRowIndex;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* @return int The ID of the worksheet
|
129 |
+
*/
|
130 |
+
public function getId()
|
131 |
+
{
|
132 |
+
// sheet index is zero-based, while ID is 1-based
|
133 |
+
return $this->externalSheet->getIndex() + 1;
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Adds data to the worksheet.
|
138 |
+
*
|
139 |
+
* @param array $dataRow Array containing data to be written. Cannot be empty.
|
140 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
141 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style.
|
142 |
+
* @return void
|
143 |
+
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
|
144 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
145 |
+
*/
|
146 |
+
public function addRow($dataRow, $style)
|
147 |
+
{
|
148 |
+
if (!$this->isEmptyRow($dataRow)) {
|
149 |
+
$this->addNonEmptyRow($dataRow, $style);
|
150 |
+
}
|
151 |
+
|
152 |
+
$this->lastWrittenRowIndex++;
|
153 |
+
}
|
154 |
+
|
155 |
+
/**
|
156 |
+
* Returns whether the given row is empty
|
157 |
+
*
|
158 |
+
* @param array $dataRow Array containing data to be written. Cannot be empty.
|
159 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
160 |
+
* @return bool Whether the given row is empty
|
161 |
+
*/
|
162 |
+
protected function isEmptyRow($dataRow)
|
163 |
+
{
|
164 |
+
$numCells = count($dataRow);
|
165 |
+
// using "reset()" instead of "$dataRow[0]" because $dataRow can be an associative array
|
166 |
+
return ($numCells === 1 && CellHelper::isEmpty(reset($dataRow)));
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Adds non empty row to the worksheet.
|
171 |
+
*
|
172 |
+
* @param array $dataRow Array containing data to be written. Cannot be empty.
|
173 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
174 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style.
|
175 |
+
* @return void
|
176 |
+
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
|
177 |
+
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
178 |
+
*/
|
179 |
+
protected function addNonEmptyRow($dataRow, $style)
|
180 |
+
{
|
181 |
+
$cellNumber = 0;
|
182 |
+
$rowIndex = $this->lastWrittenRowIndex + 1;
|
183 |
+
$numCells = count($dataRow);
|
184 |
+
|
185 |
+
$rowXML = '<row r="' . $rowIndex . '" spans="1:' . $numCells . '">';
|
186 |
+
|
187 |
+
foreach($dataRow as $cellValue) {
|
188 |
+
$rowXML .= $this->getCellXML($rowIndex, $cellNumber, $cellValue, $style->getId());
|
189 |
+
$cellNumber++;
|
190 |
+
}
|
191 |
+
|
192 |
+
$rowXML .= '</row>';
|
193 |
+
|
194 |
+
$wasWriteSuccessful = fwrite($this->sheetFilePointer, $rowXML);
|
195 |
+
if ($wasWriteSuccessful === false) {
|
196 |
+
throw new IOException("Unable to write data in {$this->worksheetFilePath}");
|
197 |
+
}
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* Build and return xml for a single cell.
|
202 |
+
*
|
203 |
+
* @param int $rowIndex
|
204 |
+
* @param int $cellNumber
|
205 |
+
* @param mixed $cellValue
|
206 |
+
* @param int $styleId
|
207 |
+
* @return string
|
208 |
+
* @throws InvalidArgumentException If the given value cannot be processed
|
209 |
+
*/
|
210 |
+
protected function getCellXML($rowIndex, $cellNumber, $cellValue, $styleId)
|
211 |
+
{
|
212 |
+
$columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber);
|
213 |
+
$cellXML = '<c r="' . $columnIndex . $rowIndex . '"';
|
214 |
+
$cellXML .= ' s="' . $styleId . '"';
|
215 |
+
|
216 |
+
if (CellHelper::isNonEmptyString($cellValue)) {
|
217 |
+
$cellXML .= $this->getCellXMLFragmentForNonEmptyString($cellValue);
|
218 |
+
} else if (CellHelper::isBoolean($cellValue)) {
|
219 |
+
$cellXML .= ' t="b"><v>' . intval($cellValue) . '</v></c>';
|
220 |
+
} else if (CellHelper::isNumeric($cellValue)) {
|
221 |
+
$cellXML .= '><v>' . $cellValue . '</v></c>';
|
222 |
+
} else if (empty($cellValue)) {
|
223 |
+
if ($this->styleHelper->shouldApplyStyleOnEmptyCell($styleId)) {
|
224 |
+
$cellXML .= '/>';
|
225 |
+
} else {
|
226 |
+
// don't write empty cells that do no need styling
|
227 |
+
// NOTE: not appending to $cellXML is the right behavior!!
|
228 |
+
$cellXML = '';
|
229 |
+
}
|
230 |
+
} else {
|
231 |
+
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue));
|
232 |
+
}
|
233 |
+
|
234 |
+
return $cellXML;
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Returns the XML fragment for a cell containing a non empty string
|
239 |
+
*
|
240 |
+
* @param string $cellValue The cell value
|
241 |
+
* @return string The XML fragment representing the cell
|
242 |
+
* @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell
|
243 |
+
*/
|
244 |
+
protected function getCellXMLFragmentForNonEmptyString($cellValue)
|
245 |
+
{
|
246 |
+
if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) {
|
247 |
+
throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)');
|
248 |
+
}
|
249 |
+
|
250 |
+
if ($this->shouldUseInlineStrings) {
|
251 |
+
$cellXMLFragment = ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>';
|
252 |
+
} else {
|
253 |
+
$sharedStringId = $this->sharedStringsHelper->writeString($cellValue);
|
254 |
+
$cellXMLFragment = ' t="s"><v>' . $sharedStringId . '</v></c>';
|
255 |
+
}
|
256 |
+
|
257 |
+
return $cellXMLFragment;
|
258 |
+
}
|
259 |
+
|
260 |
+
/**
|
261 |
+
* Closes the worksheet
|
262 |
+
*
|
263 |
+
* @return void
|
264 |
+
*/
|
265 |
+
public function close()
|
266 |
+
{
|
267 |
+
if (!is_resource($this->sheetFilePointer)) {
|
268 |
+
return;
|
269 |
+
}
|
270 |
+
|
271 |
+
fwrite($this->sheetFilePointer, '</sheetData>');
|
272 |
+
fwrite($this->sheetFilePointer, '</worksheet>');
|
273 |
+
fclose($this->sheetFilePointer);
|
274 |
+
}
|
275 |
+
}
|
app/Services/Spout/Writer/XLSX/Writer.php
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace Box\Spout\Writer\XLSX;
|
4 |
+
|
5 |
+
use Box\Spout\Writer\AbstractMultiSheetsWriter;
|
6 |
+
use Box\Spout\Writer\Style\StyleBuilder;
|
7 |
+
use Box\Spout\Writer\XLSX\Internal\Workbook;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Class Writer
|
11 |
+
* This class provides base support to write data to XLSX files
|
12 |
+
*
|
13 |
+
* @package Box\Spout\Writer\XLSX
|
14 |
+
*/
|
15 |
+
class Writer extends AbstractMultiSheetsWriter
|
16 |
+
{
|
17 |
+
/** Default style font values */
|
18 |
+
const DEFAULT_FONT_SIZE = 12;
|
19 |
+
const DEFAULT_FONT_NAME = 'Calibri';
|
20 |
+
|
21 |
+
/** @var string Content-Type value for the header */
|
22 |
+
protected static $headerContentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
23 |
+
|
24 |
+
/** @var string Temporary folder where the files to create the XLSX will be stored */
|
25 |
+
protected $tempFolder;
|
26 |
+
|
27 |
+
/** @var bool Whether inline or shared strings should be used - inline string is more memory efficient */
|
28 |
+
protected $shouldUseInlineStrings = true;
|
29 |
+
|
30 |
+
/** @var Internal\Workbook The workbook for the XLSX file */
|
31 |
+
protected $book;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Sets a custom temporary folder for creating intermediate files/folders.
|
35 |
+
* This must be set before opening the writer.
|
36 |
+
*
|
37 |
+
* @api
|
38 |
+
* @param string $tempFolder Temporary folder where the files to create the XLSX will be stored
|
39 |
+
* @return Writer
|
40 |
+
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
|
41 |
+
*/
|
42 |
+
public function setTempFolder($tempFolder)
|
43 |
+
{
|
44 |
+
$this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
|
45 |
+
|
46 |
+
$this->tempFolder = $tempFolder;
|
47 |
+
return $this;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Use inline string to be more memory efficient. If set to false, it will use shared strings.
|
52 |
+
* This must be set before opening the writer.
|
53 |
+
*
|
54 |
+
* @api
|
55 |
+
* @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
|
56 |
+
* @return Writer
|
57 |
+
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
|
58 |
+
*/
|
59 |
+
public function setShouldUseInlineStrings($shouldUseInlineStrings)
|
60 |
+
{
|
61 |
+
$this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
|
62 |
+
|
63 |
+
$this->shouldUseInlineStrings = $shouldUseInlineStrings;
|
64 |
+
return $this;
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Configures the write and sets the current sheet pointer to a new sheet.
|
69 |
+
*
|
70 |
+
* @return void
|
71 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to open the file for writing
|
72 |
+
*/
|
73 |
+
protected function openWriter()
|
74 |
+
{
|
75 |
+
if (!$this->book) {
|
76 |
+
$tempFolder = ($this->tempFolder) ? : sys_get_temp_dir();
|
77 |
+
$this->book = new Workbook($tempFolder, $this->shouldUseInlineStrings, $this->shouldCreateNewSheetsAutomatically, $this->defaultRowStyle);
|
78 |
+
$this->book->addNewSheetAndMakeItCurrent();
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* @return Internal\Workbook The workbook representing the file to be written
|
84 |
+
*/
|
85 |
+
protected function getWorkbook()
|
86 |
+
{
|
87 |
+
return $this->book;
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Adds data to the currently opened writer.
|
92 |
+
* If shouldCreateNewSheetsAutomatically option is set to true, it will handle pagination
|
93 |
+
* with the creation of new worksheets if one worksheet has reached its maximum capicity.
|
94 |
+
*
|
95 |
+
* @param array $dataRow Array containing data to be written.
|
96 |
+
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
97 |
+
* @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row.
|
98 |
+
* @return void
|
99 |
+
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the book is not created yet
|
100 |
+
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
|
101 |
+
*/
|
102 |
+
protected function addRowToWriter(array $dataRow, $style)
|
103 |
+
{
|
104 |
+
$this->throwIfBookIsNotAvailable();
|
105 |
+
$this->book->addRowToCurrentWorksheet($dataRow, $style);
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Returns the default style to be applied to rows.
|
110 |
+
*
|
111 |
+
* @return \Box\Spout\Writer\Style\Style
|
112 |
+
*/
|
113 |
+
protected function getDefaultRowStyle()
|
114 |
+
{
|
115 |
+
return (new StyleBuilder())
|
116 |
+
->setFontSize(self::DEFAULT_FONT_SIZE)
|
117 |
+
->setFontName(self::DEFAULT_FONT_NAME)
|
118 |
+
->build();
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Closes the writer, preventing any additional writing.
|
123 |
+
*
|
124 |
+
* @return void
|
125 |
+
*/
|
126 |
+
protected function closeWriter()
|
127 |
+
{
|
128 |
+
if ($this->book) {
|
129 |
+
$this->book->close($this->filePointer);
|
130 |
+
}
|
131 |
+
}
|
132 |
+
}
|
app/Services/csv/LICENSE
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
The MIT License (MIT)
|
2 |
-
|
3 |
-
Copyright (c) 2013-2015 ignace nyamagana butera
|
4 |
-
|
5 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6 |
-
this software and associated documentation files (the "Software"), to deal in
|
7 |
-
the Software without restriction, including without limitation the rights to
|
8 |
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9 |
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
10 |
-
subject to the following conditions:
|
11 |
-
|
12 |
-
The above copyright notice and this permission notice shall be included in all
|
13 |
-
copies or substantial portions of the Software.
|
14 |
-
|
15 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17 |
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18 |
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19 |
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20 |
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/autoload.php
DELETED
@@ -1,23 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
spl_autoload_register(function ($class) {
|
4 |
-
|
5 |
-
$prefix = 'League\Csv\\';
|
6 |
-
|
7 |
-
if (0 !== strpos($class, $prefix)) {
|
8 |
-
return;
|
9 |
-
}
|
10 |
-
|
11 |
-
$file = __DIR__
|
12 |
-
.DIRECTORY_SEPARATOR
|
13 |
-
.'src'
|
14 |
-
.DIRECTORY_SEPARATOR
|
15 |
-
.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen($prefix)))
|
16 |
-
.'.php';
|
17 |
-
|
18 |
-
if (! is_readable($file)) {
|
19 |
-
return;
|
20 |
-
}
|
21 |
-
|
22 |
-
require $file;
|
23 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/composer.json
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "league/csv",
|
3 |
-
"type": "library",
|
4 |
-
"description" : "Csv data manipulation made easy in PHP",
|
5 |
-
"keywords": ["csv", "import", "export", "read", "write", "filter"],
|
6 |
-
"license": "MIT",
|
7 |
-
"homepage" : "http://csv.thephpleague.com",
|
8 |
-
"authors": [
|
9 |
-
{
|
10 |
-
"name" : "Ignace Nyamagana Butera",
|
11 |
-
"email" : "nyamsprod@gmail.com",
|
12 |
-
"homepage" : "https://github.com/nyamsprod/",
|
13 |
-
"role" : "Developer"
|
14 |
-
}
|
15 |
-
],
|
16 |
-
"support": {
|
17 |
-
"forum": "https://groups.google.com/forum/#!forum/thephpleague",
|
18 |
-
"issues": "https://github.com/thephpleague/csv/issues"
|
19 |
-
},
|
20 |
-
"require": {
|
21 |
-
"php" : ">=5.4.0",
|
22 |
-
"ext-mbstring" : "*"
|
23 |
-
},
|
24 |
-
"require-dev": {
|
25 |
-
"phpunit/phpunit" : "^4.0",
|
26 |
-
"fabpot/php-cs-fixer": "^1.9"
|
27 |
-
},
|
28 |
-
"autoload": {
|
29 |
-
"psr-4": {
|
30 |
-
"League\\Csv\\": "src"
|
31 |
-
}
|
32 |
-
},
|
33 |
-
"autoload-dev": {
|
34 |
-
"psr-4": {
|
35 |
-
"League\\Csv\\Test\\": "test",
|
36 |
-
"lib\\": "examples\\lib"
|
37 |
-
}
|
38 |
-
},
|
39 |
-
"scripts": {
|
40 |
-
"test": "vendor/bin/phpunit; vendor/bin/php-cs-fixer fix -v --diff --dry-run;"
|
41 |
-
},
|
42 |
-
"extra": {
|
43 |
-
"branch-alias": {
|
44 |
-
"dev-master": "7.2-dev"
|
45 |
-
}
|
46 |
-
}
|
47 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/AbstractCsv.php
DELETED
@@ -1,322 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv;
|
14 |
-
|
15 |
-
use InvalidArgumentException;
|
16 |
-
use Iterator;
|
17 |
-
use IteratorAggregate;
|
18 |
-
use JsonSerializable;
|
19 |
-
use League\Csv\Config\Controls;
|
20 |
-
use League\Csv\Config\Output;
|
21 |
-
use League\Csv\Modifier\QueryFilter;
|
22 |
-
use League\Csv\Modifier\StreamFilter;
|
23 |
-
use SplFileInfo;
|
24 |
-
use SplFileObject;
|
25 |
-
use SplTempFileObject;
|
26 |
-
|
27 |
-
/**
|
28 |
-
* An abstract class to enable basic CSV manipulation
|
29 |
-
*
|
30 |
-
* @package League.csv
|
31 |
-
* @since 4.0.0
|
32 |
-
*
|
33 |
-
*/
|
34 |
-
abstract class AbstractCsv implements JsonSerializable, IteratorAggregate
|
35 |
-
{
|
36 |
-
use Controls;
|
37 |
-
|
38 |
-
use Output;
|
39 |
-
|
40 |
-
use QueryFilter;
|
41 |
-
|
42 |
-
use StreamFilter;
|
43 |
-
|
44 |
-
/**
|
45 |
-
* UTF-8 BOM sequence
|
46 |
-
*/
|
47 |
-
const BOM_UTF8 = "\xEF\xBB\xBF";
|
48 |
-
|
49 |
-
/**
|
50 |
-
* UTF-16 BE BOM sequence
|
51 |
-
*/
|
52 |
-
const BOM_UTF16_BE = "\xFE\xFF";
|
53 |
-
|
54 |
-
/**
|
55 |
-
* UTF-16 LE BOM sequence
|
56 |
-
*/
|
57 |
-
const BOM_UTF16_LE = "\xFF\xFE";
|
58 |
-
|
59 |
-
/**
|
60 |
-
* UTF-32 BE BOM sequence
|
61 |
-
*/
|
62 |
-
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
|
63 |
-
|
64 |
-
/**
|
65 |
-
* UTF-32 LE BOM sequence
|
66 |
-
*/
|
67 |
-
const BOM_UTF32_LE = "\x00\x00\xFF\xFE";
|
68 |
-
|
69 |
-
/**
|
70 |
-
* The constructor path
|
71 |
-
*
|
72 |
-
* can be a SplFileInfo object or the string path to a file
|
73 |
-
*
|
74 |
-
* @var SplFileObject|string
|
75 |
-
*/
|
76 |
-
protected $path;
|
77 |
-
|
78 |
-
/**
|
79 |
-
* The file open mode flag
|
80 |
-
*
|
81 |
-
* @var string
|
82 |
-
*/
|
83 |
-
protected $open_mode;
|
84 |
-
|
85 |
-
/**
|
86 |
-
* Default SplFileObject flags settings
|
87 |
-
*
|
88 |
-
* @var int
|
89 |
-
*/
|
90 |
-
protected $defaultFlags;
|
91 |
-
|
92 |
-
/**
|
93 |
-
* Creates a new instance
|
94 |
-
*
|
95 |
-
* The path must be an SplFileInfo object
|
96 |
-
* an object that implements the `__toString` method
|
97 |
-
* a path to a file
|
98 |
-
*
|
99 |
-
* @param SplFileObject|string $path The file path
|
100 |
-
* @param string $open_mode the file open mode flag
|
101 |
-
*/
|
102 |
-
protected function __construct($path, $open_mode = 'r+')
|
103 |
-
{
|
104 |
-
$this->defaultFlags = SplFileObject::READ_CSV | SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY;
|
105 |
-
$this->flags = $this->defaultFlags;
|
106 |
-
$this->open_mode = strtolower($open_mode);
|
107 |
-
$this->path = $path;
|
108 |
-
$this->initStreamFilter($this->path);
|
109 |
-
}
|
110 |
-
|
111 |
-
/**
|
112 |
-
* The destructor
|
113 |
-
*/
|
114 |
-
public function __destruct()
|
115 |
-
{
|
116 |
-
$this->path = null;
|
117 |
-
}
|
118 |
-
|
119 |
-
/**
|
120 |
-
* Returns the CSV Iterator
|
121 |
-
*
|
122 |
-
* @return SplFileObject
|
123 |
-
*/
|
124 |
-
public function getIterator()
|
125 |
-
{
|
126 |
-
$iterator = $this->path;
|
127 |
-
if (!$iterator instanceof SplFileObject) {
|
128 |
-
$iterator = new SplFileObject($this->getStreamFilterPath(), $this->open_mode);
|
129 |
-
}
|
130 |
-
$iterator->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
|
131 |
-
$iterator->setFlags($this->flags);
|
132 |
-
|
133 |
-
return $iterator;
|
134 |
-
}
|
135 |
-
|
136 |
-
/**
|
137 |
-
* Returns the CSV Iterator for conversion
|
138 |
-
*
|
139 |
-
* @return Iterator
|
140 |
-
*/
|
141 |
-
protected function getConversionIterator()
|
142 |
-
{
|
143 |
-
$iterator = $this->getIterator();
|
144 |
-
$iterator->setFlags($this->defaultFlags);
|
145 |
-
$iterator = $this->applyBomStripping($iterator);
|
146 |
-
$iterator = $this->applyIteratorFilter($iterator);
|
147 |
-
$iterator = $this->applyIteratorSortBy($iterator);
|
148 |
-
|
149 |
-
return $this->applyIteratorInterval($iterator);
|
150 |
-
}
|
151 |
-
|
152 |
-
/**
|
153 |
-
* Creates a {@link AbstractCsv} from a string
|
154 |
-
*
|
155 |
-
* The path can be:
|
156 |
-
* - an SplFileInfo,
|
157 |
-
* - a SplFileObject,
|
158 |
-
* - an object that implements the `__toString` method,
|
159 |
-
* - a string
|
160 |
-
*
|
161 |
-
* BUT NOT a SplTempFileObject
|
162 |
-
*
|
163 |
-
* <code>
|
164 |
-
*<?php
|
165 |
-
* $csv = new Reader::createFromPath('/path/to/file.csv', 'a+');
|
166 |
-
* $csv = new Reader::createFromPath(new SplFileInfo('/path/to/file.csv'));
|
167 |
-
* $csv = new Reader::createFromPath(new SplFileObject('/path/to/file.csv'), 'rb');
|
168 |
-
*
|
169 |
-
* ?>
|
170 |
-
* </code>
|
171 |
-
*
|
172 |
-
* @param mixed $path file path
|
173 |
-
* @param string $open_mode the file open mode flag
|
174 |
-
*
|
175 |
-
* @throws InvalidArgumentException If $path is a \SplTempFileObject object
|
176 |
-
*
|
177 |
-
* @return static
|
178 |
-
*/
|
179 |
-
public static function createFromPath($path, $open_mode = 'r+')
|
180 |
-
{
|
181 |
-
if ($path instanceof SplTempFileObject) {
|
182 |
-
throw new InvalidArgumentException('an `SplTempFileObject` object does not contain a valid path');
|
183 |
-
}
|
184 |
-
|
185 |
-
if ($path instanceof SplFileInfo) {
|
186 |
-
$path = $path->getPath().'/'.$path->getBasename();
|
187 |
-
}
|
188 |
-
|
189 |
-
return new static(static::validateString($path), $open_mode);
|
190 |
-
}
|
191 |
-
|
192 |
-
/**
|
193 |
-
* validate a string
|
194 |
-
*
|
195 |
-
* @param mixed $str the value to evaluate as a string
|
196 |
-
*
|
197 |
-
* @throws InvalidArgumentException if the submitted data can not be converted to string
|
198 |
-
*
|
199 |
-
* @return string
|
200 |
-
*/
|
201 |
-
protected static function validateString($str)
|
202 |
-
{
|
203 |
-
if (is_string($str) || (is_object($str) && method_exists($str, '__toString'))) {
|
204 |
-
return (string) $str;
|
205 |
-
}
|
206 |
-
|
207 |
-
throw new InvalidArgumentException('Expected data must be a string or stringable');
|
208 |
-
}
|
209 |
-
|
210 |
-
/**
|
211 |
-
* Creates a {@link AbstractCsv} from a SplFileObject
|
212 |
-
*
|
213 |
-
* The path can be:
|
214 |
-
* - a SplFileObject,
|
215 |
-
* - a SplTempFileObject
|
216 |
-
*
|
217 |
-
* <code>
|
218 |
-
*<?php
|
219 |
-
* $csv = new Writer::createFromFileObject(new SplFileInfo('/path/to/file.csv'));
|
220 |
-
* $csv = new Writer::createFromFileObject(new SplTempFileObject);
|
221 |
-
*
|
222 |
-
* ?>
|
223 |
-
* </code>
|
224 |
-
*
|
225 |
-
* @param SplFileObject $file
|
226 |
-
*
|
227 |
-
* @return static
|
228 |
-
*/
|
229 |
-
public static function createFromFileObject(SplFileObject $file)
|
230 |
-
{
|
231 |
-
return new static($file);
|
232 |
-
}
|
233 |
-
|
234 |
-
/**
|
235 |
-
* Creates a {@link AbstractCsv} from a string
|
236 |
-
*
|
237 |
-
* The string must be an object that implements the `__toString` method,
|
238 |
-
* or a string
|
239 |
-
*
|
240 |
-
* @param string $str the string
|
241 |
-
* @param string $newline the newline character
|
242 |
-
*
|
243 |
-
* @return static
|
244 |
-
*/
|
245 |
-
public static function createFromString($str, $newline = "\n")
|
246 |
-
{
|
247 |
-
$file = new SplTempFileObject();
|
248 |
-
$file->fwrite(static::validateString($str));
|
249 |
-
|
250 |
-
$csv = static::createFromFileObject($file);
|
251 |
-
$csv->setNewline($newline);
|
252 |
-
|
253 |
-
return $csv;
|
254 |
-
}
|
255 |
-
|
256 |
-
/**
|
257 |
-
* Creates a {@link AbstractCsv} instance from another {@link AbstractCsv} object
|
258 |
-
*
|
259 |
-
* @param string $class_name the class to be instantiated
|
260 |
-
* @param string $open_mode the file open mode flag
|
261 |
-
*
|
262 |
-
* @return static
|
263 |
-
*/
|
264 |
-
protected function newInstance($class_name, $open_mode)
|
265 |
-
{
|
266 |
-
$csv = new $class_name($this->path, $open_mode);
|
267 |
-
$csv->delimiter = $this->delimiter;
|
268 |
-
$csv->enclosure = $this->enclosure;
|
269 |
-
$csv->escape = $this->escape;
|
270 |
-
$csv->encodingFrom = $this->encodingFrom;
|
271 |
-
$csv->flags = $this->flags;
|
272 |
-
$csv->input_bom = $this->input_bom;
|
273 |
-
$csv->output_bom = $this->output_bom;
|
274 |
-
$csv->newline = $this->newline;
|
275 |
-
|
276 |
-
return $csv;
|
277 |
-
}
|
278 |
-
|
279 |
-
/**
|
280 |
-
* Creates a {@link Writer} instance from a {@link AbstractCsv} object
|
281 |
-
*
|
282 |
-
* @param string $open_mode the file open mode flag
|
283 |
-
*
|
284 |
-
* @return Writer
|
285 |
-
*/
|
286 |
-
public function newWriter($open_mode = 'r+')
|
287 |
-
{
|
288 |
-
return $this->newInstance('\League\Csv\Writer', $open_mode);
|
289 |
-
}
|
290 |
-
|
291 |
-
/**
|
292 |
-
* Creates a {@link Reader} instance from a {@link AbstractCsv} object
|
293 |
-
*
|
294 |
-
* @param string $open_mode the file open mode flag
|
295 |
-
*
|
296 |
-
* @return Reader
|
297 |
-
*/
|
298 |
-
public function newReader($open_mode = 'r+')
|
299 |
-
{
|
300 |
-
return $this->newInstance('\League\Csv\Reader', $open_mode);
|
301 |
-
}
|
302 |
-
|
303 |
-
/**
|
304 |
-
* Validate the submitted integer
|
305 |
-
*
|
306 |
-
* @param int $int
|
307 |
-
* @param int $minValue
|
308 |
-
* @param string $errorMessage
|
309 |
-
*
|
310 |
-
* @throws InvalidArgumentException If the value is invalid
|
311 |
-
*
|
312 |
-
* @return int
|
313 |
-
*/
|
314 |
-
protected function filterInteger($int, $minValue, $errorMessage)
|
315 |
-
{
|
316 |
-
if (false === ($int = filter_var($int, FILTER_VALIDATE_INT, ['options' => ['min_range' => $minValue]]))) {
|
317 |
-
throw new InvalidArgumentException($errorMessage);
|
318 |
-
}
|
319 |
-
|
320 |
-
return $int;
|
321 |
-
}
|
322 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Config/Controls.php
DELETED
@@ -1,288 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Config;
|
14 |
-
|
15 |
-
use CallbackFilterIterator;
|
16 |
-
use InvalidArgumentException;
|
17 |
-
use LimitIterator;
|
18 |
-
use SplFileObject;
|
19 |
-
|
20 |
-
/**
|
21 |
-
* A trait to configure and check CSV file and content
|
22 |
-
*
|
23 |
-
* @package League.csv
|
24 |
-
* @since 6.0.0
|
25 |
-
*
|
26 |
-
*/
|
27 |
-
trait Controls
|
28 |
-
{
|
29 |
-
/**
|
30 |
-
* the field delimiter (one character only)
|
31 |
-
*
|
32 |
-
* @var string
|
33 |
-
*/
|
34 |
-
protected $delimiter = ',';
|
35 |
-
|
36 |
-
/**
|
37 |
-
* the field enclosure character (one character only)
|
38 |
-
*
|
39 |
-
* @var string
|
40 |
-
*/
|
41 |
-
protected $enclosure = '"';
|
42 |
-
|
43 |
-
/**
|
44 |
-
* the field escape character (one character only)
|
45 |
-
*
|
46 |
-
* @var string
|
47 |
-
*/
|
48 |
-
protected $escape = '\\';
|
49 |
-
|
50 |
-
/**
|
51 |
-
* the \SplFileObject flags holder
|
52 |
-
*
|
53 |
-
* @var int
|
54 |
-
*/
|
55 |
-
protected $flags;
|
56 |
-
|
57 |
-
/**
|
58 |
-
* newline character
|
59 |
-
*
|
60 |
-
* @var string
|
61 |
-
*/
|
62 |
-
protected $newline = "\n";
|
63 |
-
|
64 |
-
/**
|
65 |
-
* Sets the field delimiter
|
66 |
-
*
|
67 |
-
* @param string $delimiter
|
68 |
-
*
|
69 |
-
* @throws InvalidArgumentException If $delimiter is not a single character
|
70 |
-
*
|
71 |
-
* @return $this
|
72 |
-
*/
|
73 |
-
public function setDelimiter($delimiter)
|
74 |
-
{
|
75 |
-
if (!$this->isValidCsvControls($delimiter)) {
|
76 |
-
throw new InvalidArgumentException('The delimiter must be a single character');
|
77 |
-
}
|
78 |
-
$this->delimiter = $delimiter;
|
79 |
-
|
80 |
-
return $this;
|
81 |
-
}
|
82 |
-
|
83 |
-
/**
|
84 |
-
* Tell whether the submitted string is a valid CSV Control character
|
85 |
-
*
|
86 |
-
* @param string $str The submitted string
|
87 |
-
*
|
88 |
-
* @return bool
|
89 |
-
*/
|
90 |
-
protected function isValidCsvControls($str)
|
91 |
-
{
|
92 |
-
return 1 == mb_strlen($str);
|
93 |
-
}
|
94 |
-
|
95 |
-
/**
|
96 |
-
* Returns the current field delimiter
|
97 |
-
*
|
98 |
-
* @return string
|
99 |
-
*/
|
100 |
-
public function getDelimiter()
|
101 |
-
{
|
102 |
-
return $this->delimiter;
|
103 |
-
}
|
104 |
-
|
105 |
-
/**
|
106 |
-
* Detects the CSV file delimiters
|
107 |
-
*
|
108 |
-
* Returns a associative array where each key represents
|
109 |
-
* the number of occurences and each value a delimiter with the
|
110 |
-
* given occurence
|
111 |
-
*
|
112 |
-
* This method returns incorrect informations when two delimiters
|
113 |
-
* have the same occurrence count
|
114 |
-
*
|
115 |
-
* DEPRECATION WARNING! This method will be removed in the next major point release
|
116 |
-
*
|
117 |
-
* @deprecated deprecated since version 7.2
|
118 |
-
*
|
119 |
-
* @param int $nb_rows
|
120 |
-
* @param string[] $delimiters additional delimiters
|
121 |
-
*
|
122 |
-
* @return string[]
|
123 |
-
*/
|
124 |
-
public function detectDelimiterList($nb_rows = 1, array $delimiters = [])
|
125 |
-
{
|
126 |
-
$delimiters = array_merge([$this->delimiter, ',', ';', "\t"], $delimiters);
|
127 |
-
$stats = $this->fetchDelimitersOccurrence($delimiters, $nb_rows);
|
128 |
-
|
129 |
-
return array_flip(array_filter($stats));
|
130 |
-
}
|
131 |
-
|
132 |
-
/**
|
133 |
-
* Detect Delimiters occurences in the CSV
|
134 |
-
*
|
135 |
-
* Returns a associative array where each key represents
|
136 |
-
* a valid delimiter and each value the number of occurences
|
137 |
-
*
|
138 |
-
* @param string[] $delimiters the delimiters to consider
|
139 |
-
* @param int $nb_rows Detection is made using $nb_rows of the CSV
|
140 |
-
*
|
141 |
-
* @throws InvalidArgumentException If $nb_rows value is invalid
|
142 |
-
*
|
143 |
-
* @return array
|
144 |
-
*/
|
145 |
-
public function fetchDelimitersOccurrence(array $delimiters, $nb_rows = 1)
|
146 |
-
{
|
147 |
-
if (!($nb_rows = filter_var($nb_rows, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]))) {
|
148 |
-
throw new InvalidArgumentException('The number of rows to consider must be a valid positive integer');
|
149 |
-
}
|
150 |
-
|
151 |
-
$filterRow = function ($row) {
|
152 |
-
return is_array($row) && count($row) > 1;
|
153 |
-
};
|
154 |
-
$delimiters = array_unique(array_filter($delimiters, [$this, 'isValidCsvControls']));
|
155 |
-
$csv = $this->getIterator();
|
156 |
-
$res = [];
|
157 |
-
foreach ($delimiters as $delim) {
|
158 |
-
$csv->setCsvControl($delim, $this->enclosure, $this->escape);
|
159 |
-
$iterator = new CallbackFilterIterator(new LimitIterator($csv, 0, $nb_rows), $filterRow);
|
160 |
-
$res[$delim] = count(iterator_to_array($iterator, false), COUNT_RECURSIVE);
|
161 |
-
}
|
162 |
-
arsort($res, SORT_NUMERIC);
|
163 |
-
|
164 |
-
return $res;
|
165 |
-
}
|
166 |
-
|
167 |
-
/**
|
168 |
-
* Returns the CSV Iterator
|
169 |
-
*
|
170 |
-
* @return SplFileObject
|
171 |
-
*/
|
172 |
-
abstract public function getIterator();
|
173 |
-
|
174 |
-
/**
|
175 |
-
* Sets the field enclosure
|
176 |
-
*
|
177 |
-
* @param string $enclosure
|
178 |
-
*
|
179 |
-
* @throws InvalidArgumentException If $enclosure is not a single character
|
180 |
-
*
|
181 |
-
* @return $this
|
182 |
-
*/
|
183 |
-
public function setEnclosure($enclosure)
|
184 |
-
{
|
185 |
-
if (!$this->isValidCsvControls($enclosure)) {
|
186 |
-
throw new InvalidArgumentException('The enclosure must be a single character');
|
187 |
-
}
|
188 |
-
$this->enclosure = $enclosure;
|
189 |
-
|
190 |
-
return $this;
|
191 |
-
}
|
192 |
-
|
193 |
-
/**
|
194 |
-
* Returns the current field enclosure
|
195 |
-
*
|
196 |
-
* @return string
|
197 |
-
*/
|
198 |
-
public function getEnclosure()
|
199 |
-
{
|
200 |
-
return $this->enclosure;
|
201 |
-
}
|
202 |
-
|
203 |
-
/**
|
204 |
-
* Sets the field escape character
|
205 |
-
*
|
206 |
-
* @param string $escape
|
207 |
-
*
|
208 |
-
* @throws InvalidArgumentException If $escape is not a single character
|
209 |
-
*
|
210 |
-
* @return $this
|
211 |
-
*/
|
212 |
-
public function setEscape($escape)
|
213 |
-
{
|
214 |
-
if (!$this->isValidCsvControls($escape)) {
|
215 |
-
throw new InvalidArgumentException('The escape character must be a single character');
|
216 |
-
}
|
217 |
-
$this->escape = $escape;
|
218 |
-
|
219 |
-
return $this;
|
220 |
-
}
|
221 |
-
|
222 |
-
/**
|
223 |
-
* Returns the current field escape character
|
224 |
-
*
|
225 |
-
* @return string
|
226 |
-
*/
|
227 |
-
public function getEscape()
|
228 |
-
{
|
229 |
-
return $this->escape;
|
230 |
-
}
|
231 |
-
|
232 |
-
/**
|
233 |
-
* Sets the Flags associated to the CSV SplFileObject
|
234 |
-
*
|
235 |
-
* @param int $flags
|
236 |
-
*
|
237 |
-
* @throws InvalidArgumentException If the argument is not a valid integer
|
238 |
-
*
|
239 |
-
* @return $this
|
240 |
-
*/
|
241 |
-
public function setFlags($flags)
|
242 |
-
{
|
243 |
-
$flags = $this->filterInteger($flags, 0, 'you should use a `SplFileObject` Constant');
|
244 |
-
$this->flags = $flags | SplFileObject::READ_CSV;
|
245 |
-
|
246 |
-
return $this;
|
247 |
-
}
|
248 |
-
|
249 |
-
/**
|
250 |
-
* @inheritdoc
|
251 |
-
*/
|
252 |
-
abstract protected function filterInteger($int, $minValue, $errorMessage);
|
253 |
-
|
254 |
-
/**
|
255 |
-
* Returns the file Flags
|
256 |
-
*
|
257 |
-
* @return int
|
258 |
-
*/
|
259 |
-
public function getFlags()
|
260 |
-
{
|
261 |
-
return $this->flags;
|
262 |
-
}
|
263 |
-
|
264 |
-
|
265 |
-
/**
|
266 |
-
* Sets the newline sequence characters
|
267 |
-
*
|
268 |
-
* @param string $newline
|
269 |
-
*
|
270 |
-
* @return static
|
271 |
-
*/
|
272 |
-
public function setNewline($newline)
|
273 |
-
{
|
274 |
-
$this->newline = (string) $newline;
|
275 |
-
|
276 |
-
return $this;
|
277 |
-
}
|
278 |
-
|
279 |
-
/**
|
280 |
-
* Returns the current newline sequence characters
|
281 |
-
*
|
282 |
-
* @return string
|
283 |
-
*/
|
284 |
-
public function getNewline()
|
285 |
-
{
|
286 |
-
return $this->newline;
|
287 |
-
}
|
288 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Config/Output.php
DELETED
@@ -1,282 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Config;
|
14 |
-
|
15 |
-
use DomDocument;
|
16 |
-
use InvalidArgumentException;
|
17 |
-
use Iterator;
|
18 |
-
use League\Csv\Modifier\MapIterator;
|
19 |
-
use SplFileObject;
|
20 |
-
|
21 |
-
/**
|
22 |
-
* A trait to output CSV
|
23 |
-
*
|
24 |
-
* @package League.csv
|
25 |
-
* @since 6.3.0
|
26 |
-
*
|
27 |
-
*/
|
28 |
-
trait Output
|
29 |
-
{
|
30 |
-
/**
|
31 |
-
* Charset Encoding for the CSV
|
32 |
-
*
|
33 |
-
* @var string
|
34 |
-
*/
|
35 |
-
protected $encodingFrom = 'UTF-8';
|
36 |
-
|
37 |
-
/**
|
38 |
-
* The Input file BOM character
|
39 |
-
* @var string
|
40 |
-
*/
|
41 |
-
protected $input_bom;
|
42 |
-
|
43 |
-
/**
|
44 |
-
* The Output file BOM character
|
45 |
-
* @var string
|
46 |
-
*/
|
47 |
-
protected $output_bom;
|
48 |
-
|
49 |
-
/**
|
50 |
-
* Returns the CSV Iterator
|
51 |
-
*
|
52 |
-
* @return Iterator
|
53 |
-
*/
|
54 |
-
abstract protected function getConversionIterator();
|
55 |
-
|
56 |
-
/**
|
57 |
-
* Returns the CSV Iterator
|
58 |
-
*
|
59 |
-
* @return Iterator
|
60 |
-
*/
|
61 |
-
abstract public function getIterator();
|
62 |
-
|
63 |
-
/**
|
64 |
-
* Sets the CSV encoding charset
|
65 |
-
*
|
66 |
-
* @param string $str
|
67 |
-
*
|
68 |
-
* @return static
|
69 |
-
*/
|
70 |
-
public function setEncodingFrom($str)
|
71 |
-
{
|
72 |
-
$str = str_replace('_', '-', $str);
|
73 |
-
$str = filter_var($str, FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH]);
|
74 |
-
if (empty($str)) {
|
75 |
-
throw new InvalidArgumentException('you should use a valid charset');
|
76 |
-
}
|
77 |
-
$this->encodingFrom = strtoupper($str);
|
78 |
-
|
79 |
-
return $this;
|
80 |
-
}
|
81 |
-
|
82 |
-
/**
|
83 |
-
* Gets the source CSV encoding charset
|
84 |
-
*
|
85 |
-
* @return string
|
86 |
-
*/
|
87 |
-
public function getEncodingFrom()
|
88 |
-
{
|
89 |
-
return $this->encodingFrom;
|
90 |
-
}
|
91 |
-
|
92 |
-
/**
|
93 |
-
* Sets the BOM sequence to prepend the CSV on output
|
94 |
-
*
|
95 |
-
* @param string $str The BOM sequence
|
96 |
-
*
|
97 |
-
* @return static
|
98 |
-
*/
|
99 |
-
public function setOutputBOM($str = null)
|
100 |
-
{
|
101 |
-
if (empty($str)) {
|
102 |
-
$this->output_bom = null;
|
103 |
-
|
104 |
-
return $this;
|
105 |
-
}
|
106 |
-
|
107 |
-
$this->output_bom = (string) $str;
|
108 |
-
|
109 |
-
return $this;
|
110 |
-
}
|
111 |
-
|
112 |
-
/**
|
113 |
-
* Returns the BOM sequence in use on Output methods
|
114 |
-
*
|
115 |
-
* @return string
|
116 |
-
*/
|
117 |
-
public function getOutputBOM()
|
118 |
-
{
|
119 |
-
return $this->output_bom;
|
120 |
-
}
|
121 |
-
|
122 |
-
/**
|
123 |
-
* Returns the BOM sequence of the given CSV
|
124 |
-
*
|
125 |
-
* @return string
|
126 |
-
*/
|
127 |
-
public function getInputBOM()
|
128 |
-
{
|
129 |
-
if (! $this->input_bom) {
|
130 |
-
$bom = [
|
131 |
-
self::BOM_UTF32_BE, self::BOM_UTF32_LE,
|
132 |
-
self::BOM_UTF16_BE, self::BOM_UTF16_LE, self::BOM_UTF8,
|
133 |
-
];
|
134 |
-
$csv = $this->getIterator();
|
135 |
-
$csv->setFlags(SplFileObject::READ_CSV);
|
136 |
-
$csv->rewind();
|
137 |
-
$line = $csv->fgets();
|
138 |
-
$res = array_filter($bom, function ($sequence) use ($line) {
|
139 |
-
return strpos($line, $sequence) === 0;
|
140 |
-
});
|
141 |
-
|
142 |
-
$this->input_bom = array_shift($res);
|
143 |
-
}
|
144 |
-
|
145 |
-
return $this->input_bom;
|
146 |
-
}
|
147 |
-
|
148 |
-
/**
|
149 |
-
* Outputs all data on the CSV file
|
150 |
-
*
|
151 |
-
* @param string $filename CSV downloaded name if present adds extra headers
|
152 |
-
*
|
153 |
-
* @return int Returns the number of characters read from the handle
|
154 |
-
* and passed through to the output.
|
155 |
-
*/
|
156 |
-
public function output($filename = null)
|
157 |
-
{
|
158 |
-
if (!is_null($filename)) {
|
159 |
-
$filename = filter_var($filename, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
|
160 |
-
header('Content-Type: application/octet-stream');
|
161 |
-
header('Content-Transfer-Encoding: binary');
|
162 |
-
header("Content-Disposition: attachment; filename=\"$filename\"");
|
163 |
-
}
|
164 |
-
|
165 |
-
return $this->fpassthru();
|
166 |
-
}
|
167 |
-
|
168 |
-
/**
|
169 |
-
* Outputs all data from the CSV
|
170 |
-
*
|
171 |
-
* @return int Returns the number of characters read from the handle
|
172 |
-
* and passed through to the output.
|
173 |
-
*/
|
174 |
-
protected function fpassthru()
|
175 |
-
{
|
176 |
-
$bom = '';
|
177 |
-
$input_bom = $this->getInputBOM();
|
178 |
-
if ($this->output_bom && $input_bom != $this->output_bom) {
|
179 |
-
$bom = $this->output_bom;
|
180 |
-
}
|
181 |
-
$csv = $this->getIterator();
|
182 |
-
$csv->setFlags(SplFileObject::READ_CSV);
|
183 |
-
$csv->rewind();
|
184 |
-
if (!empty($bom)) {
|
185 |
-
$csv->fseek(mb_strlen($input_bom));
|
186 |
-
}
|
187 |
-
echo $bom;
|
188 |
-
$res = $csv->fpassthru();
|
189 |
-
|
190 |
-
return $res + strlen($bom);
|
191 |
-
}
|
192 |
-
|
193 |
-
/**
|
194 |
-
* Retrieves the CSV content
|
195 |
-
*
|
196 |
-
* @return string
|
197 |
-
*/
|
198 |
-
public function __toString()
|
199 |
-
{
|
200 |
-
ob_start();
|
201 |
-
$this->fpassthru();
|
202 |
-
|
203 |
-
return ob_get_clean();
|
204 |
-
}
|
205 |
-
|
206 |
-
/**
|
207 |
-
* JsonSerializable Interface
|
208 |
-
*
|
209 |
-
* @return array
|
210 |
-
*/
|
211 |
-
public function jsonSerialize()
|
212 |
-
{
|
213 |
-
return iterator_to_array($this->convertToUtf8($this->getConversionIterator()), false);
|
214 |
-
}
|
215 |
-
|
216 |
-
/**
|
217 |
-
* Convert Csv file into UTF-8
|
218 |
-
*
|
219 |
-
* @param Iterator $iterator
|
220 |
-
*
|
221 |
-
* @return Iterator
|
222 |
-
*/
|
223 |
-
protected function convertToUtf8(Iterator $iterator)
|
224 |
-
{
|
225 |
-
if (strpos($this->encodingFrom, 'UTF-8') !== false) {
|
226 |
-
return $iterator;
|
227 |
-
}
|
228 |
-
|
229 |
-
return new MapIterator($iterator, function ($row) {
|
230 |
-
foreach ($row as &$value) {
|
231 |
-
$value = mb_convert_encoding($value, 'UTF-8', $this->encodingFrom);
|
232 |
-
}
|
233 |
-
unset($value);
|
234 |
-
|
235 |
-
return $row;
|
236 |
-
});
|
237 |
-
}
|
238 |
-
|
239 |
-
/**
|
240 |
-
* Returns a HTML table representation of the CSV Table
|
241 |
-
*
|
242 |
-
* @param string $class_name optional classname
|
243 |
-
*
|
244 |
-
* @return string
|
245 |
-
*/
|
246 |
-
public function toHTML($class_name = 'table-csv-data')
|
247 |
-
{
|
248 |
-
$doc = $this->toXML('table', 'tr', 'td');
|
249 |
-
$doc->documentElement->setAttribute('class', $class_name);
|
250 |
-
|
251 |
-
return $doc->saveHTML($doc->documentElement);
|
252 |
-
}
|
253 |
-
|
254 |
-
/**
|
255 |
-
* Transforms a CSV into a XML
|
256 |
-
*
|
257 |
-
* @param string $root_name XML root node name
|
258 |
-
* @param string $row_name XML row node name
|
259 |
-
* @param string $cell_name XML cell node name
|
260 |
-
*
|
261 |
-
* @return DomDocument
|
262 |
-
*/
|
263 |
-
public function toXML($root_name = 'csv', $row_name = 'row', $cell_name = 'cell')
|
264 |
-
{
|
265 |
-
$doc = new DomDocument('1.0', 'UTF-8');
|
266 |
-
$root = $doc->createElement($root_name);
|
267 |
-
$iterator = $this->convertToUtf8($this->getConversionIterator());
|
268 |
-
foreach ($iterator as $row) {
|
269 |
-
$item = $doc->createElement($row_name);
|
270 |
-
array_walk($row, function ($value) use (&$item, $doc, $cell_name) {
|
271 |
-
$content = $doc->createTextNode($value);
|
272 |
-
$cell = $doc->createElement($cell_name);
|
273 |
-
$cell->appendChild($content);
|
274 |
-
$item->appendChild($cell);
|
275 |
-
});
|
276 |
-
$root->appendChild($item);
|
277 |
-
}
|
278 |
-
$doc->appendChild($root);
|
279 |
-
|
280 |
-
return $doc;
|
281 |
-
}
|
282 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Exception/InvalidRowException.php
DELETED
@@ -1,69 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Exception;
|
14 |
-
|
15 |
-
/**
|
16 |
-
* Thrown when a data is not validated prior to insertion
|
17 |
-
*
|
18 |
-
* @package League.csv
|
19 |
-
* @since 7.0.0
|
20 |
-
*
|
21 |
-
*/
|
22 |
-
class InvalidRowException extends \InvalidArgumentException
|
23 |
-
{
|
24 |
-
/**
|
25 |
-
* Validator which did not validated the data
|
26 |
-
* @var string
|
27 |
-
*/
|
28 |
-
private $name;
|
29 |
-
|
30 |
-
/**
|
31 |
-
* Validator Data which caused the error
|
32 |
-
* @var array
|
33 |
-
*/
|
34 |
-
private $data;
|
35 |
-
|
36 |
-
/**
|
37 |
-
* New Instance
|
38 |
-
*
|
39 |
-
* @param string $name validator name
|
40 |
-
* @param array $data invalid data
|
41 |
-
* @param string $message exception message
|
42 |
-
*/
|
43 |
-
public function __construct($name, array $data = [], $message = '')
|
44 |
-
{
|
45 |
-
parent::__construct($message);
|
46 |
-
$this->name = $name;
|
47 |
-
$this->data = $data;
|
48 |
-
}
|
49 |
-
|
50 |
-
/**
|
51 |
-
* return the validator name
|
52 |
-
*
|
53 |
-
* @return string
|
54 |
-
*/
|
55 |
-
public function getName()
|
56 |
-
{
|
57 |
-
return $this->name;
|
58 |
-
}
|
59 |
-
|
60 |
-
/**
|
61 |
-
* return the invalid data submitted
|
62 |
-
*
|
63 |
-
* @return array
|
64 |
-
*/
|
65 |
-
public function getData()
|
66 |
-
{
|
67 |
-
return $this->data;
|
68 |
-
}
|
69 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Modifier/MapIterator.php
DELETED
@@ -1,56 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Modifier;
|
14 |
-
|
15 |
-
use Iterator;
|
16 |
-
use IteratorIterator;
|
17 |
-
|
18 |
-
/**
|
19 |
-
* A simple MapIterator
|
20 |
-
*
|
21 |
-
* @package League.csv
|
22 |
-
* @since 3.3.0
|
23 |
-
* @internal used internally to modify CSV content
|
24 |
-
*
|
25 |
-
*/
|
26 |
-
class MapIterator extends IteratorIterator
|
27 |
-
{
|
28 |
-
/**
|
29 |
-
* The function to be apply on all InnerIterator element
|
30 |
-
*
|
31 |
-
* @var callable
|
32 |
-
*/
|
33 |
-
private $callable;
|
34 |
-
|
35 |
-
/**
|
36 |
-
* The Constructor
|
37 |
-
*
|
38 |
-
* @param Iterator $iterator
|
39 |
-
* @param callable $callable
|
40 |
-
*/
|
41 |
-
public function __construct(Iterator $iterator, callable $callable)
|
42 |
-
{
|
43 |
-
parent::__construct($iterator);
|
44 |
-
$this->callable = $callable;
|
45 |
-
}
|
46 |
-
|
47 |
-
/**
|
48 |
-
* Get the value of the current element
|
49 |
-
*/
|
50 |
-
public function current()
|
51 |
-
{
|
52 |
-
$iterator = $this->getInnerIterator();
|
53 |
-
|
54 |
-
return call_user_func($this->callable, $iterator->current(), $iterator->key(), $iterator);
|
55 |
-
}
|
56 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Modifier/QueryFilter.php
DELETED
@@ -1,352 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Modifier;
|
14 |
-
|
15 |
-
use ArrayObject;
|
16 |
-
use CallbackFilterIterator;
|
17 |
-
use Iterator;
|
18 |
-
use LimitIterator;
|
19 |
-
|
20 |
-
/**
|
21 |
-
* A Trait to Query rows against a SplFileObject
|
22 |
-
*
|
23 |
-
* @package League.csv
|
24 |
-
* @since 4.2.1
|
25 |
-
*
|
26 |
-
*/
|
27 |
-
trait QueryFilter
|
28 |
-
{
|
29 |
-
/**
|
30 |
-
* Callables to filter the iterator
|
31 |
-
*
|
32 |
-
* @var callable[]
|
33 |
-
*/
|
34 |
-
protected $iterator_filters = [];
|
35 |
-
|
36 |
-
/**
|
37 |
-
* Callables to sort the iterator
|
38 |
-
*
|
39 |
-
* @var callable[]
|
40 |
-
*/
|
41 |
-
protected $iterator_sort_by = [];
|
42 |
-
|
43 |
-
/**
|
44 |
-
* iterator Offset
|
45 |
-
*
|
46 |
-
* @var int
|
47 |
-
*/
|
48 |
-
protected $iterator_offset = 0;
|
49 |
-
|
50 |
-
/**
|
51 |
-
* iterator maximum length
|
52 |
-
*
|
53 |
-
* @var int
|
54 |
-
*/
|
55 |
-
protected $iterator_limit = -1;
|
56 |
-
|
57 |
-
/**
|
58 |
-
* Stripping BOM status
|
59 |
-
*
|
60 |
-
* @var boolean
|
61 |
-
*/
|
62 |
-
protected $strip_bom = false;
|
63 |
-
|
64 |
-
/**
|
65 |
-
* Stripping BOM setter
|
66 |
-
*
|
67 |
-
* @param bool $status
|
68 |
-
*
|
69 |
-
* @return $this
|
70 |
-
*/
|
71 |
-
public function stripBom($status)
|
72 |
-
{
|
73 |
-
$this->strip_bom = (bool) $status;
|
74 |
-
|
75 |
-
return $this;
|
76 |
-
}
|
77 |
-
|
78 |
-
/**
|
79 |
-
* Tell whether we can strip or not the leading BOM sequence
|
80 |
-
*
|
81 |
-
* @return bool
|
82 |
-
*/
|
83 |
-
protected function isBomStrippable()
|
84 |
-
{
|
85 |
-
$bom = $this->getInputBom();
|
86 |
-
|
87 |
-
return ! empty($bom) && $this->strip_bom;
|
88 |
-
}
|
89 |
-
|
90 |
-
/**
|
91 |
-
* {@inheritdoc}
|
92 |
-
*/
|
93 |
-
abstract public function getInputBom();
|
94 |
-
|
95 |
-
/**
|
96 |
-
* Set LimitIterator Offset
|
97 |
-
*
|
98 |
-
* @param $offset
|
99 |
-
*
|
100 |
-
* @return $this
|
101 |
-
*/
|
102 |
-
public function setOffset($offset = 0)
|
103 |
-
{
|
104 |
-
$this->iterator_offset = $this->filterInteger($offset, 0, 'the offset must be a positive integer or 0');
|
105 |
-
|
106 |
-
return $this;
|
107 |
-
}
|
108 |
-
|
109 |
-
/**
|
110 |
-
* @inheritdoc
|
111 |
-
*/
|
112 |
-
abstract protected function filterInteger($int, $minValue, $errorMessage);
|
113 |
-
|
114 |
-
/**
|
115 |
-
* Set LimitIterator Count
|
116 |
-
*
|
117 |
-
* @param int $limit
|
118 |
-
*
|
119 |
-
* @return $this
|
120 |
-
*/
|
121 |
-
public function setLimit($limit = -1)
|
122 |
-
{
|
123 |
-
$this->iterator_limit = $this->filterInteger($limit, -1, 'the limit must an integer greater or equals to -1');
|
124 |
-
|
125 |
-
return $this;
|
126 |
-
}
|
127 |
-
|
128 |
-
/**
|
129 |
-
* Set an Iterator sorting callable function
|
130 |
-
*
|
131 |
-
* @param callable $callable
|
132 |
-
*
|
133 |
-
* @return $this
|
134 |
-
*/
|
135 |
-
public function addSortBy(callable $callable)
|
136 |
-
{
|
137 |
-
$this->iterator_sort_by[] = $callable;
|
138 |
-
|
139 |
-
return $this;
|
140 |
-
}
|
141 |
-
|
142 |
-
/**
|
143 |
-
* Remove a callable from the collection
|
144 |
-
*
|
145 |
-
* @param callable $callable
|
146 |
-
*
|
147 |
-
* @return $this
|
148 |
-
*/
|
149 |
-
public function removeSortBy(callable $callable)
|
150 |
-
{
|
151 |
-
$res = array_search($callable, $this->iterator_sort_by, true);
|
152 |
-
unset($this->iterator_sort_by[$res]);
|
153 |
-
|
154 |
-
return $this;
|
155 |
-
}
|
156 |
-
|
157 |
-
/**
|
158 |
-
* Detect if the callable is already registered
|
159 |
-
*
|
160 |
-
* @param callable $callable
|
161 |
-
*
|
162 |
-
* @return bool
|
163 |
-
*/
|
164 |
-
public function hasSortBy(callable $callable)
|
165 |
-
{
|
166 |
-
return false !== array_search($callable, $this->iterator_sort_by, true);
|
167 |
-
}
|
168 |
-
|
169 |
-
/**
|
170 |
-
* Remove all registered callable
|
171 |
-
*
|
172 |
-
* @return $this
|
173 |
-
*/
|
174 |
-
public function clearSortBy()
|
175 |
-
{
|
176 |
-
$this->iterator_sort_by = [];
|
177 |
-
|
178 |
-
return $this;
|
179 |
-
}
|
180 |
-
|
181 |
-
/**
|
182 |
-
* Set the Iterator filter method
|
183 |
-
*
|
184 |
-
* @param callable $callable
|
185 |
-
*
|
186 |
-
* @return $this
|
187 |
-
*/
|
188 |
-
public function addFilter(callable $callable)
|
189 |
-
{
|
190 |
-
$this->iterator_filters[] = $callable;
|
191 |
-
|
192 |
-
return $this;
|
193 |
-
}
|
194 |
-
|
195 |
-
/**
|
196 |
-
* Remove a filter from the callable collection
|
197 |
-
*
|
198 |
-
* @param callable $callable
|
199 |
-
*
|
200 |
-
* @return $this
|
201 |
-
*/
|
202 |
-
public function removeFilter(callable $callable)
|
203 |
-
{
|
204 |
-
$res = array_search($callable, $this->iterator_filters, true);
|
205 |
-
unset($this->iterator_filters[$res]);
|
206 |
-
|
207 |
-
return $this;
|
208 |
-
}
|
209 |
-
|
210 |
-
/**
|
211 |
-
* Detect if the callable filter is already registered
|
212 |
-
*
|
213 |
-
* @param callable $callable
|
214 |
-
*
|
215 |
-
* @return bool
|
216 |
-
*/
|
217 |
-
public function hasFilter(callable $callable)
|
218 |
-
{
|
219 |
-
return false !== array_search($callable, $this->iterator_filters, true);
|
220 |
-
}
|
221 |
-
|
222 |
-
/**
|
223 |
-
* Remove all registered callable filter
|
224 |
-
*
|
225 |
-
* @return $this
|
226 |
-
*/
|
227 |
-
public function clearFilter()
|
228 |
-
{
|
229 |
-
$this->iterator_filters = [];
|
230 |
-
|
231 |
-
return $this;
|
232 |
-
}
|
233 |
-
|
234 |
-
/**
|
235 |
-
* Remove the BOM sequence from the CSV
|
236 |
-
*
|
237 |
-
* @param Iterator $iterator
|
238 |
-
*
|
239 |
-
* @return \Iterator
|
240 |
-
*/
|
241 |
-
protected function applyBomStripping(Iterator $iterator)
|
242 |
-
{
|
243 |
-
if (! $this->strip_bom) {
|
244 |
-
return $iterator;
|
245 |
-
}
|
246 |
-
|
247 |
-
if (! $this->isBomStrippable()) {
|
248 |
-
$this->strip_bom = false;
|
249 |
-
|
250 |
-
return $iterator;
|
251 |
-
}
|
252 |
-
|
253 |
-
$this->strip_bom = false;
|
254 |
-
|
255 |
-
return $this->getStripBomIterator($iterator);
|
256 |
-
}
|
257 |
-
|
258 |
-
/**
|
259 |
-
* Return the Iterator without the BOM sequence
|
260 |
-
*
|
261 |
-
* @param Iterator $iterator
|
262 |
-
*
|
263 |
-
* @return Iterator
|
264 |
-
*/
|
265 |
-
protected function getStripBomIterator(Iterator $iterator)
|
266 |
-
{
|
267 |
-
$bom = $this->getInputBom();
|
268 |
-
|
269 |
-
return new MapIterator($iterator, function ($row, $index) use ($bom) {
|
270 |
-
if (0 == $index) {
|
271 |
-
$row[0] = mb_substr($row[0], mb_strlen($bom));
|
272 |
-
$enclosure = $this->getEnclosure();
|
273 |
-
//enclosure should be remove when a BOM sequence is stripped
|
274 |
-
if ($row[0][0] === $enclosure && mb_substr($row[0], -1, 1) == $enclosure) {
|
275 |
-
$row[0] = mb_substr($row[0], 1, -1);
|
276 |
-
}
|
277 |
-
}
|
278 |
-
|
279 |
-
return $row;
|
280 |
-
});
|
281 |
-
}
|
282 |
-
|
283 |
-
/**
|
284 |
-
* {@inheritdoc}
|
285 |
-
*/
|
286 |
-
abstract public function getEnclosure();
|
287 |
-
|
288 |
-
/**
|
289 |
-
* Filter the Iterator
|
290 |
-
*
|
291 |
-
* @param \Iterator $iterator
|
292 |
-
*
|
293 |
-
* @return \Iterator
|
294 |
-
*/
|
295 |
-
protected function applyIteratorFilter(Iterator $iterator)
|
296 |
-
{
|
297 |
-
foreach ($this->iterator_filters as $callable) {
|
298 |
-
$iterator = new CallbackFilterIterator($iterator, $callable);
|
299 |
-
}
|
300 |
-
$this->clearFilter();
|
301 |
-
|
302 |
-
return $iterator;
|
303 |
-
}
|
304 |
-
|
305 |
-
/**
|
306 |
-
* Sort the Iterator
|
307 |
-
*
|
308 |
-
* @param \Iterator $iterator
|
309 |
-
*
|
310 |
-
* @return \Iterator
|
311 |
-
*/
|
312 |
-
protected function applyIteratorInterval(Iterator $iterator)
|
313 |
-
{
|
314 |
-
if (0 == $this->iterator_offset && -1 == $this->iterator_limit) {
|
315 |
-
return $iterator;
|
316 |
-
}
|
317 |
-
$offset = $this->iterator_offset;
|
318 |
-
$limit = $this->iterator_limit;
|
319 |
-
$this->iterator_limit = -1;
|
320 |
-
$this->iterator_offset = 0;
|
321 |
-
|
322 |
-
return new LimitIterator($iterator, $offset, $limit);
|
323 |
-
}
|
324 |
-
|
325 |
-
/**
|
326 |
-
* Sort the Iterator
|
327 |
-
*
|
328 |
-
* @param \Iterator $iterator
|
329 |
-
*
|
330 |
-
* @return \Iterator
|
331 |
-
*/
|
332 |
-
protected function applyIteratorSortBy(Iterator $iterator)
|
333 |
-
{
|
334 |
-
if (! $this->iterator_sort_by) {
|
335 |
-
return $iterator;
|
336 |
-
}
|
337 |
-
$obj = new ArrayObject(iterator_to_array($iterator, false));
|
338 |
-
$obj->uasort(function ($rowA, $rowB) {
|
339 |
-
$sortRes = 0;
|
340 |
-
foreach ($this->iterator_sort_by as $callable) {
|
341 |
-
if (0 !== ($sortRes = call_user_func($callable, $rowA, $rowB))) {
|
342 |
-
break;
|
343 |
-
}
|
344 |
-
}
|
345 |
-
|
346 |
-
return $sortRes;
|
347 |
-
});
|
348 |
-
$this->clearSortBy();
|
349 |
-
|
350 |
-
return $obj->getIterator();
|
351 |
-
}
|
352 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Modifier/RowFilter.php
DELETED
@@ -1,179 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Modifier;
|
14 |
-
|
15 |
-
use League\Csv\Exception\InvalidRowException;
|
16 |
-
|
17 |
-
/**
|
18 |
-
* Trait to format and validate the row before insertion
|
19 |
-
*
|
20 |
-
* @package League.csv
|
21 |
-
* @since 7.0.0
|
22 |
-
*
|
23 |
-
*/
|
24 |
-
trait RowFilter
|
25 |
-
{
|
26 |
-
/**
|
27 |
-
* Callables to validate the row before insertion
|
28 |
-
*
|
29 |
-
* @var callable[]
|
30 |
-
*/
|
31 |
-
protected $validators = [];
|
32 |
-
|
33 |
-
/**
|
34 |
-
* Callables to format the row before insertion
|
35 |
-
*
|
36 |
-
* @var callable[]
|
37 |
-
*/
|
38 |
-
protected $formatters = [];
|
39 |
-
|
40 |
-
/**
|
41 |
-
* add a formatter to the collection
|
42 |
-
*
|
43 |
-
* @param callable $callable
|
44 |
-
*
|
45 |
-
* @return $this
|
46 |
-
*/
|
47 |
-
public function addFormatter(callable $callable)
|
48 |
-
{
|
49 |
-
$this->formatters[] = $callable;
|
50 |
-
|
51 |
-
return $this;
|
52 |
-
}
|
53 |
-
|
54 |
-
/**
|
55 |
-
* Remove a formatter from the collection
|
56 |
-
*
|
57 |
-
* @param callable $callable
|
58 |
-
*
|
59 |
-
* @return $this
|
60 |
-
*/
|
61 |
-
public function removeFormatter(callable $callable)
|
62 |
-
{
|
63 |
-
$res = array_search($callable, $this->formatters, true);
|
64 |
-
unset($this->formatters[$res]);
|
65 |
-
|
66 |
-
return $this;
|
67 |
-
}
|
68 |
-
|
69 |
-
/**
|
70 |
-
* Detect if the formatter is already registered
|
71 |
-
*
|
72 |
-
* @param callable $callable
|
73 |
-
*
|
74 |
-
* @return bool
|
75 |
-
*/
|
76 |
-
public function hasFormatter(callable $callable)
|
77 |
-
{
|
78 |
-
return false !== array_search($callable, $this->formatters, true);
|
79 |
-
}
|
80 |
-
|
81 |
-
/**
|
82 |
-
* Remove all registered formatter
|
83 |
-
*
|
84 |
-
* @return $this
|
85 |
-
*/
|
86 |
-
public function clearFormatters()
|
87 |
-
{
|
88 |
-
$this->formatters = [];
|
89 |
-
|
90 |
-
return $this;
|
91 |
-
}
|
92 |
-
|
93 |
-
/**
|
94 |
-
* add a Validator to the collection
|
95 |
-
*
|
96 |
-
* @param callable $callable
|
97 |
-
* @param string $name the rule name
|
98 |
-
*
|
99 |
-
* @return $this
|
100 |
-
*/
|
101 |
-
public function addValidator(callable $callable, $name)
|
102 |
-
{
|
103 |
-
$this->validators[$name] = $callable;
|
104 |
-
|
105 |
-
return $this;
|
106 |
-
}
|
107 |
-
|
108 |
-
/**
|
109 |
-
* Remove a validator from the collection
|
110 |
-
*
|
111 |
-
* @param string $name the validator name
|
112 |
-
*
|
113 |
-
* @return $this
|
114 |
-
*/
|
115 |
-
public function removeValidator($name)
|
116 |
-
{
|
117 |
-
if (array_key_exists($name, $this->validators)) {
|
118 |
-
unset($this->validators[$name]);
|
119 |
-
}
|
120 |
-
|
121 |
-
return $this;
|
122 |
-
}
|
123 |
-
|
124 |
-
/**
|
125 |
-
* Detect if a validator is already registered
|
126 |
-
*
|
127 |
-
* @param string $name the validator name
|
128 |
-
*
|
129 |
-
* @return bool
|
130 |
-
*/
|
131 |
-
public function hasValidator($name)
|
132 |
-
{
|
133 |
-
return array_key_exists($name, $this->validators);
|
134 |
-
}
|
135 |
-
|
136 |
-
/**
|
137 |
-
* Remove all registered validators
|
138 |
-
*
|
139 |
-
* @return $this
|
140 |
-
*/
|
141 |
-
public function clearValidators()
|
142 |
-
{
|
143 |
-
$this->validators = [];
|
144 |
-
|
145 |
-
return $this;
|
146 |
-
}
|
147 |
-
|
148 |
-
/**
|
149 |
-
* Format the given row
|
150 |
-
*
|
151 |
-
* @param array|string $row
|
152 |
-
*
|
153 |
-
* @return array
|
154 |
-
*/
|
155 |
-
protected function formatRow(array $row)
|
156 |
-
{
|
157 |
-
foreach ($this->formatters as $formatter) {
|
158 |
-
$row = call_user_func($formatter, $row);
|
159 |
-
}
|
160 |
-
|
161 |
-
return $row;
|
162 |
-
}
|
163 |
-
|
164 |
-
/**
|
165 |
-
* validate a row
|
166 |
-
*
|
167 |
-
* @param array $row
|
168 |
-
*
|
169 |
-
* @throws InvalidRowException If the validation failed
|
170 |
-
*/
|
171 |
-
protected function validateRow(array $row)
|
172 |
-
{
|
173 |
-
foreach ($this->validators as $name => $validator) {
|
174 |
-
if (true !== call_user_func($validator, $row)) {
|
175 |
-
throw new InvalidRowException($name, $row, 'row validation failed');
|
176 |
-
}
|
177 |
-
}
|
178 |
-
}
|
179 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Modifier/StreamFilter.php
DELETED
@@ -1,294 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Modifier;
|
14 |
-
|
15 |
-
use LogicException;
|
16 |
-
use OutOfBoundsException;
|
17 |
-
|
18 |
-
/**
|
19 |
-
* A Trait to ease PHP Stream Filters manipulation
|
20 |
-
* with a SplFileObject
|
21 |
-
*
|
22 |
-
* @package League.csv
|
23 |
-
* @since 6.0.0
|
24 |
-
*
|
25 |
-
*/
|
26 |
-
trait StreamFilter
|
27 |
-
{
|
28 |
-
/**
|
29 |
-
* collection of stream filters
|
30 |
-
*
|
31 |
-
* @var array
|
32 |
-
*/
|
33 |
-
protected $stream_filters = [];
|
34 |
-
|
35 |
-
/**
|
36 |
-
* Stream filtering mode to apply on all filters
|
37 |
-
*
|
38 |
-
* @var int
|
39 |
-
*/
|
40 |
-
protected $stream_filter_mode = STREAM_FILTER_ALL;
|
41 |
-
|
42 |
-
/**
|
43 |
-
*the real path
|
44 |
-
*
|
45 |
-
* @var string the real path to the file
|
46 |
-
*
|
47 |
-
*/
|
48 |
-
protected $stream_uri;
|
49 |
-
|
50 |
-
/**
|
51 |
-
* PHP Stream Filter Regex
|
52 |
-
*
|
53 |
-
* @var string
|
54 |
-
*/
|
55 |
-
protected $stream_regex = ',^
|
56 |
-
php://filter/
|
57 |
-
(?P<mode>:?read=|write=)? # The resource open mode
|
58 |
-
(?P<filters>.*?) # The resource registered filters
|
59 |
-
/resource=(?P<resource>.*) # The resource path
|
60 |
-
$,ix';
|
61 |
-
|
62 |
-
/**
|
63 |
-
* Internal path setter
|
64 |
-
*
|
65 |
-
* The path must be an SplFileInfo object
|
66 |
-
* an object that implements the `__toString` method
|
67 |
-
* a path to a file
|
68 |
-
*
|
69 |
-
* @param \SplFileObject|string $path The file path
|
70 |
-
*/
|
71 |
-
protected function initStreamFilter($path)
|
72 |
-
{
|
73 |
-
$this->stream_filters = [];
|
74 |
-
if (! is_string($path)) {
|
75 |
-
$this->stream_uri = null;
|
76 |
-
|
77 |
-
return;
|
78 |
-
}
|
79 |
-
|
80 |
-
if (! preg_match($this->stream_regex, $path, $matches)) {
|
81 |
-
$this->stream_uri = $path;
|
82 |
-
|
83 |
-
return;
|
84 |
-
}
|
85 |
-
$this->stream_uri = $matches['resource'];
|
86 |
-
$this->stream_filters = explode('|', $matches['filters']);
|
87 |
-
$this->stream_filter_mode = $this->fetchStreamModeAsInt($matches['mode']);
|
88 |
-
}
|
89 |
-
|
90 |
-
/**
|
91 |
-
* Get the stream mode
|
92 |
-
*
|
93 |
-
* @param string $mode
|
94 |
-
*
|
95 |
-
* @return int
|
96 |
-
*/
|
97 |
-
protected function fetchStreamModeAsInt($mode)
|
98 |
-
{
|
99 |
-
$mode = strtolower($mode);
|
100 |
-
$mode = rtrim($mode, '=');
|
101 |
-
if ('write' == $mode) {
|
102 |
-
return STREAM_FILTER_WRITE;
|
103 |
-
}
|
104 |
-
|
105 |
-
if ('read' == $mode) {
|
106 |
-
return STREAM_FILTER_READ;
|
107 |
-
}
|
108 |
-
|
109 |
-
return STREAM_FILTER_ALL;
|
110 |
-
}
|
111 |
-
|
112 |
-
/**
|
113 |
-
* Check if the trait methods can be used
|
114 |
-
*
|
115 |
-
* @throws LogicException If the API can not be use
|
116 |
-
*/
|
117 |
-
protected function assertStreamable()
|
118 |
-
{
|
119 |
-
if (!is_string($this->stream_uri)) {
|
120 |
-
throw new LogicException('The stream filter API can not be used');
|
121 |
-
}
|
122 |
-
}
|
123 |
-
|
124 |
-
/**
|
125 |
-
* Tells whether the stream filter capabilities can be used
|
126 |
-
*
|
127 |
-
* @return bool
|
128 |
-
*/
|
129 |
-
public function isActiveStreamFilter()
|
130 |
-
{
|
131 |
-
return is_string($this->stream_uri);
|
132 |
-
}
|
133 |
-
|
134 |
-
/**
|
135 |
-
* stream filter mode Setter
|
136 |
-
*
|
137 |
-
* Set the new Stream Filter mode and remove all
|
138 |
-
* previously attached stream filters
|
139 |
-
*
|
140 |
-
* @param int $mode
|
141 |
-
*
|
142 |
-
* @throws OutOfBoundsException If the mode is invalid
|
143 |
-
*
|
144 |
-
* @return $this
|
145 |
-
*/
|
146 |
-
public function setStreamFilterMode($mode)
|
147 |
-
{
|
148 |
-
$this->assertStreamable();
|
149 |
-
if (!in_array($mode, [STREAM_FILTER_ALL, STREAM_FILTER_READ, STREAM_FILTER_WRITE])) {
|
150 |
-
throw new OutOfBoundsException('the $mode should be a valid `STREAM_FILTER_*` constant');
|
151 |
-
}
|
152 |
-
|
153 |
-
$this->stream_filter_mode = $mode;
|
154 |
-
$this->stream_filters = [];
|
155 |
-
|
156 |
-
return $this;
|
157 |
-
}
|
158 |
-
|
159 |
-
/**
|
160 |
-
* stream filter mode getter
|
161 |
-
*
|
162 |
-
* @return int
|
163 |
-
*/
|
164 |
-
public function getStreamFilterMode()
|
165 |
-
{
|
166 |
-
$this->assertStreamable();
|
167 |
-
|
168 |
-
return $this->stream_filter_mode;
|
169 |
-
}
|
170 |
-
|
171 |
-
/**
|
172 |
-
* append a stream filter
|
173 |
-
*
|
174 |
-
* @param string $filter_name a string or an object that implements the '__toString' method
|
175 |
-
*
|
176 |
-
* @return $this
|
177 |
-
*/
|
178 |
-
public function appendStreamFilter($filter_name)
|
179 |
-
{
|
180 |
-
$this->assertStreamable();
|
181 |
-
$this->stream_filters[] = $this->sanitizeStreamFilter($filter_name);
|
182 |
-
|
183 |
-
return $this;
|
184 |
-
}
|
185 |
-
|
186 |
-
/**
|
187 |
-
* prepend a stream filter
|
188 |
-
*
|
189 |
-
* @param string $filter_name a string or an object that implements the '__toString' method
|
190 |
-
*
|
191 |
-
* @return $this
|
192 |
-
*/
|
193 |
-
public function prependStreamFilter($filter_name)
|
194 |
-
{
|
195 |
-
$this->assertStreamable();
|
196 |
-
array_unshift($this->stream_filters, $this->sanitizeStreamFilter($filter_name));
|
197 |
-
|
198 |
-
return $this;
|
199 |
-
}
|
200 |
-
|
201 |
-
/**
|
202 |
-
* Sanitize the stream filter name
|
203 |
-
*
|
204 |
-
* @param string $filter_name the stream filter name
|
205 |
-
*
|
206 |
-
* @return string
|
207 |
-
*/
|
208 |
-
protected function sanitizeStreamFilter($filter_name)
|
209 |
-
{
|
210 |
-
$this->assertStreamable();
|
211 |
-
return (string) $filter_name;
|
212 |
-
}
|
213 |
-
|
214 |
-
/**
|
215 |
-
* Detect if the stream filter is already present
|
216 |
-
*
|
217 |
-
* @param string $filter_name
|
218 |
-
*
|
219 |
-
* @return bool
|
220 |
-
*/
|
221 |
-
public function hasStreamFilter($filter_name)
|
222 |
-
{
|
223 |
-
$this->assertStreamable();
|
224 |
-
|
225 |
-
return false !== array_search($filter_name, $this->stream_filters, true);
|
226 |
-
}
|
227 |
-
|
228 |
-
/**
|
229 |
-
* Remove a filter from the collection
|
230 |
-
*
|
231 |
-
* @param string $filter_name
|
232 |
-
*
|
233 |
-
* @return $this
|
234 |
-
*/
|
235 |
-
public function removeStreamFilter($filter_name)
|
236 |
-
{
|
237 |
-
$this->assertStreamable();
|
238 |
-
$res = array_search($filter_name, $this->stream_filters, true);
|
239 |
-
if (false !== $res) {
|
240 |
-
unset($this->stream_filters[$res]);
|
241 |
-
}
|
242 |
-
|
243 |
-
return $this;
|
244 |
-
}
|
245 |
-
|
246 |
-
/**
|
247 |
-
* Remove all registered stream filter
|
248 |
-
*
|
249 |
-
* @return $this
|
250 |
-
*/
|
251 |
-
public function clearStreamFilter()
|
252 |
-
{
|
253 |
-
$this->assertStreamable();
|
254 |
-
$this->stream_filters = [];
|
255 |
-
|
256 |
-
return $this;
|
257 |
-
}
|
258 |
-
|
259 |
-
/**
|
260 |
-
* Return the filter path
|
261 |
-
*
|
262 |
-
* @return string
|
263 |
-
*/
|
264 |
-
protected function getStreamFilterPath()
|
265 |
-
{
|
266 |
-
$this->assertStreamable();
|
267 |
-
if (! $this->stream_filters) {
|
268 |
-
return $this->stream_uri;
|
269 |
-
}
|
270 |
-
|
271 |
-
return 'php://filter/'
|
272 |
-
.$this->getStreamFilterPrefix()
|
273 |
-
.implode('|', $this->stream_filters)
|
274 |
-
.'/resource='.$this->stream_uri;
|
275 |
-
}
|
276 |
-
|
277 |
-
/**
|
278 |
-
* Return PHP stream filter prefix
|
279 |
-
*
|
280 |
-
* @return string
|
281 |
-
*/
|
282 |
-
protected function getStreamFilterPrefix()
|
283 |
-
{
|
284 |
-
if (STREAM_FILTER_READ == $this->stream_filter_mode) {
|
285 |
-
return 'read=';
|
286 |
-
}
|
287 |
-
|
288 |
-
if (STREAM_FILTER_WRITE == $this->stream_filter_mode) {
|
289 |
-
return 'write=';
|
290 |
-
}
|
291 |
-
|
292 |
-
return '';
|
293 |
-
}
|
294 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Plugin/ColumnConsistencyValidator.php
DELETED
@@ -1,98 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Plugin;
|
14 |
-
|
15 |
-
use InvalidArgumentException;
|
16 |
-
|
17 |
-
/**
|
18 |
-
* A class to manage column consistency on data insertion into a CSV
|
19 |
-
*
|
20 |
-
* @package League.csv
|
21 |
-
* @since 7.0.0
|
22 |
-
*
|
23 |
-
*/
|
24 |
-
class ColumnConsistencyValidator
|
25 |
-
{
|
26 |
-
/**
|
27 |
-
* The number of column per row
|
28 |
-
*
|
29 |
-
* @var int
|
30 |
-
*/
|
31 |
-
private $columns_count = -1;
|
32 |
-
|
33 |
-
/**
|
34 |
-
* should the class detect the column count based the inserted row
|
35 |
-
*
|
36 |
-
* @var bool
|
37 |
-
*/
|
38 |
-
private $detect_columns_count = false;
|
39 |
-
|
40 |
-
/**
|
41 |
-
* Set Inserted row column count
|
42 |
-
*
|
43 |
-
* @param int $value
|
44 |
-
*
|
45 |
-
* @throws InvalidArgumentException If $value is lesser than -1
|
46 |
-
*
|
47 |
-
*/
|
48 |
-
public function setColumnsCount($value)
|
49 |
-
{
|
50 |
-
if (false === filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => -1]])) {
|
51 |
-
throw new InvalidArgumentException('the column count must an integer greater or equals to -1');
|
52 |
-
}
|
53 |
-
$this->detect_columns_count = false;
|
54 |
-
$this->columns_count = $value;
|
55 |
-
}
|
56 |
-
|
57 |
-
/**
|
58 |
-
* Column count getter
|
59 |
-
*
|
60 |
-
* @return int
|
61 |
-
*/
|
62 |
-
public function getColumnsCount()
|
63 |
-
{
|
64 |
-
return $this->columns_count;
|
65 |
-
}
|
66 |
-
|
67 |
-
/**
|
68 |
-
* The method will set the $columns_count property according to the next inserted row
|
69 |
-
* and therefore will also validate the next line whatever length it has no matter
|
70 |
-
* the current $columns_count property value.
|
71 |
-
*
|
72 |
-
*/
|
73 |
-
public function autodetectColumnsCount()
|
74 |
-
{
|
75 |
-
$this->detect_columns_count = true;
|
76 |
-
}
|
77 |
-
|
78 |
-
/**
|
79 |
-
* Is the submitted row valid
|
80 |
-
*
|
81 |
-
* @param array $row
|
82 |
-
*
|
83 |
-
* @return bool
|
84 |
-
*/
|
85 |
-
public function __invoke(array $row)
|
86 |
-
{
|
87 |
-
if ($this->detect_columns_count) {
|
88 |
-
$this->columns_count = count($row);
|
89 |
-
$this->detect_columns_count = false;
|
90 |
-
|
91 |
-
return true;
|
92 |
-
} elseif (-1 == $this->columns_count) {
|
93 |
-
return true;
|
94 |
-
}
|
95 |
-
|
96 |
-
return count($row) == $this->columns_count;
|
97 |
-
}
|
98 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Plugin/ForbiddenNullValuesValidator.php
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Plugin;
|
14 |
-
|
15 |
-
/**
|
16 |
-
* A class to validate null value handling on data insertion into a CSV
|
17 |
-
*
|
18 |
-
* @package League.csv
|
19 |
-
* @since 7.0.0
|
20 |
-
*
|
21 |
-
*/
|
22 |
-
class ForbiddenNullValuesValidator
|
23 |
-
{
|
24 |
-
/**
|
25 |
-
* Is the submitted row valid
|
26 |
-
*
|
27 |
-
* @param array $row
|
28 |
-
*
|
29 |
-
* @return bool
|
30 |
-
*/
|
31 |
-
public function __invoke(array $row)
|
32 |
-
{
|
33 |
-
$res = array_filter($row, function ($value) {
|
34 |
-
return is_null($value);
|
35 |
-
});
|
36 |
-
|
37 |
-
return !$res;
|
38 |
-
}
|
39 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Plugin/SkipNullValuesFormatter.php
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv\Plugin;
|
14 |
-
|
15 |
-
/**
|
16 |
-
* A class to remove null value from data before insertion into a CSV
|
17 |
-
*
|
18 |
-
* @package League.csv
|
19 |
-
* @since 7.0.0
|
20 |
-
*
|
21 |
-
*/
|
22 |
-
class SkipNullValuesFormatter
|
23 |
-
{
|
24 |
-
/**
|
25 |
-
* remove null value form the submitted array
|
26 |
-
*
|
27 |
-
* @param array $row
|
28 |
-
*
|
29 |
-
* @return array
|
30 |
-
*/
|
31 |
-
public function __invoke(array $row)
|
32 |
-
{
|
33 |
-
return array_filter($row, function ($value) {
|
34 |
-
return !is_null($value);
|
35 |
-
});
|
36 |
-
}
|
37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Reader.php
DELETED
@@ -1,279 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv;
|
14 |
-
|
15 |
-
use CallbackFilterIterator;
|
16 |
-
use InvalidArgumentException;
|
17 |
-
use Iterator;
|
18 |
-
use League\Csv\Modifier\MapIterator;
|
19 |
-
use LimitIterator;
|
20 |
-
use SplFileObject;
|
21 |
-
|
22 |
-
/**
|
23 |
-
* A class to manage extracting and filtering a CSV
|
24 |
-
*
|
25 |
-
* @package League.csv
|
26 |
-
* @since 3.0.0
|
27 |
-
*
|
28 |
-
*/
|
29 |
-
class Reader extends AbstractCsv
|
30 |
-
{
|
31 |
-
/**
|
32 |
-
* @inheritdoc
|
33 |
-
*/
|
34 |
-
protected $stream_filter_mode = STREAM_FILTER_READ;
|
35 |
-
|
36 |
-
/**
|
37 |
-
* Returns a Filtered Iterator
|
38 |
-
*
|
39 |
-
* DEPRECATION WARNING! This method will be removed in the next major point release
|
40 |
-
*
|
41 |
-
* @deprecated deprecated since version 7.2
|
42 |
-
*
|
43 |
-
* @return Iterator
|
44 |
-
*/
|
45 |
-
public function query(callable $callable = null)
|
46 |
-
{
|
47 |
-
return $this->fetch($callable);
|
48 |
-
}
|
49 |
-
|
50 |
-
/**
|
51 |
-
* Return a Filtered Iterator
|
52 |
-
*
|
53 |
-
* @param callable $callable a callable function to be applied to each Iterator item
|
54 |
-
*
|
55 |
-
* @return Iterator
|
56 |
-
*/
|
57 |
-
public function fetch(callable $callable = null)
|
58 |
-
{
|
59 |
-
$this->addFilter(function ($row) {
|
60 |
-
return is_array($row);
|
61 |
-
});
|
62 |
-
$iterator = $this->getIterator();
|
63 |
-
$iterator = $this->applyBomStripping($iterator);
|
64 |
-
$iterator = $this->applyIteratorFilter($iterator);
|
65 |
-
$iterator = $this->applyIteratorSortBy($iterator);
|
66 |
-
$iterator = $this->applyIteratorInterval($iterator);
|
67 |
-
if (!is_null($callable)) {
|
68 |
-
return new MapIterator($iterator, $callable);
|
69 |
-
}
|
70 |
-
|
71 |
-
return $iterator;
|
72 |
-
}
|
73 |
-
|
74 |
-
/**
|
75 |
-
* Applies a callback function on the CSV
|
76 |
-
*
|
77 |
-
* The callback function must return TRUE in order to continue
|
78 |
-
* iterating over the iterator.
|
79 |
-
*
|
80 |
-
* @param callable $callable The callback function
|
81 |
-
*
|
82 |
-
* @return int the iteration count
|
83 |
-
*/
|
84 |
-
public function each(callable $callable)
|
85 |
-
{
|
86 |
-
$index = 0;
|
87 |
-
$iterator = $this->fetch();
|
88 |
-
$iterator->rewind();
|
89 |
-
while ($iterator->valid() && true === call_user_func(
|
90 |
-
$callable,
|
91 |
-
$iterator->current(),
|
92 |
-
$iterator->key(),
|
93 |
-
$iterator
|
94 |
-
)) {
|
95 |
-
++$index;
|
96 |
-
$iterator->next();
|
97 |
-
}
|
98 |
-
|
99 |
-
return $index;
|
100 |
-
}
|
101 |
-
|
102 |
-
/**
|
103 |
-
* Returns a single row from the CSV
|
104 |
-
*
|
105 |
-
* @param int $offset
|
106 |
-
*
|
107 |
-
* @throws InvalidArgumentException If the $offset is not a valid Integer
|
108 |
-
*
|
109 |
-
* @return array
|
110 |
-
*/
|
111 |
-
public function fetchOne($offset = 0)
|
112 |
-
{
|
113 |
-
$this->setOffset($offset);
|
114 |
-
$this->setLimit(1);
|
115 |
-
$iterator = $this->fetch();
|
116 |
-
$iterator->rewind();
|
117 |
-
|
118 |
-
return (array) $iterator->current();
|
119 |
-
}
|
120 |
-
|
121 |
-
/**
|
122 |
-
* Returns a sequential array of all CSV lines
|
123 |
-
*
|
124 |
-
* The callable function will be applied to each Iterator item
|
125 |
-
*
|
126 |
-
* @param callable $callable a callable function
|
127 |
-
*
|
128 |
-
* @return array
|
129 |
-
*/
|
130 |
-
public function fetchAll(callable $callable = null)
|
131 |
-
{
|
132 |
-
return iterator_to_array($this->fetch($callable), false);
|
133 |
-
}
|
134 |
-
|
135 |
-
/**
|
136 |
-
* Returns a single column from the CSV data
|
137 |
-
*
|
138 |
-
* The callable function will be applied to each value to be return
|
139 |
-
*
|
140 |
-
* @param int $column_index field Index
|
141 |
-
* @param callable $callable a callable function
|
142 |
-
*
|
143 |
-
* @throws InvalidArgumentException If the column index is not a positive integer or 0
|
144 |
-
*
|
145 |
-
* @return array
|
146 |
-
*/
|
147 |
-
public function fetchColumn($column_index = 0, callable $callable = null)
|
148 |
-
{
|
149 |
-
if (false === filter_var($column_index, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
|
150 |
-
throw new InvalidArgumentException(
|
151 |
-
'the column index must be a positive integer or 0'
|
152 |
-
);
|
153 |
-
}
|
154 |
-
$filterColumn = function ($row) use ($column_index) {
|
155 |
-
return array_key_exists($column_index, $row);
|
156 |
-
};
|
157 |
-
$selectColumn = function ($row) use ($column_index) {
|
158 |
-
return $row[$column_index];
|
159 |
-
};
|
160 |
-
|
161 |
-
$iterator = $this->fetch($callable);
|
162 |
-
$iterator = new CallbackFilterIterator($iterator, $filterColumn);
|
163 |
-
|
164 |
-
return iterator_to_array(new MapIterator($iterator, $selectColumn), false);
|
165 |
-
}
|
166 |
-
|
167 |
-
/**
|
168 |
-
* Returns a sequential array of all CSV lines;
|
169 |
-
*
|
170 |
-
* The rows are presented as associated arrays
|
171 |
-
* The callable function will be applied to each Iterator item
|
172 |
-
*
|
173 |
-
* @param int|array $offset_or_keys the name for each key member OR the row Index to be
|
174 |
-
* used as the associated named keys
|
175 |
-
*
|
176 |
-
* @param callable $callable a callable function
|
177 |
-
*
|
178 |
-
* @throws InvalidArgumentException If the submitted keys are invalid
|
179 |
-
*
|
180 |
-
* @return Iterator
|
181 |
-
*/
|
182 |
-
public function fetchAssoc($offset_or_keys = 0, callable $callable = null)
|
183 |
-
{
|
184 |
-
$keys = $this->getAssocKeys($offset_or_keys);
|
185 |
-
$keys_count = count($keys);
|
186 |
-
$combineArray = function (array $row) use ($keys, $keys_count) {
|
187 |
-
if ($keys_count != count($row)) {
|
188 |
-
$row = array_slice(array_pad($row, $keys_count, null), 0, $keys_count);
|
189 |
-
}
|
190 |
-
|
191 |
-
return array_combine($keys, $row);
|
192 |
-
};
|
193 |
-
|
194 |
-
return iterator_to_array(new MapIterator($this->fetch($callable), $combineArray), false);
|
195 |
-
}
|
196 |
-
|
197 |
-
/**
|
198 |
-
* Selects the array to be used as key for the fetchAssoc method
|
199 |
-
*
|
200 |
-
* @param int|array $offset_or_keys the assoc key OR the row Index to be used
|
201 |
-
* as the key index
|
202 |
-
*
|
203 |
-
* @throws InvalidArgumentException If the row index and/or the resulting array is invalid
|
204 |
-
*
|
205 |
-
* @return array
|
206 |
-
*/
|
207 |
-
protected function getAssocKeys($offset_or_keys)
|
208 |
-
{
|
209 |
-
if (is_array($offset_or_keys)) {
|
210 |
-
return $this->validateAssocKeys($offset_or_keys);
|
211 |
-
}
|
212 |
-
|
213 |
-
if (false === filter_var($offset_or_keys, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])) {
|
214 |
-
throw new InvalidArgumentException('the row index must be a positive integer, 0 or a non empty array');
|
215 |
-
}
|
216 |
-
|
217 |
-
$keys = $this->getRow($offset_or_keys);
|
218 |
-
$keys = $this->validateAssocKeys($keys);
|
219 |
-
$filterOutRow = function ($row, $rowIndex) use ($offset_or_keys) {
|
220 |
-
return is_array($row) && $rowIndex != $offset_or_keys;
|
221 |
-
};
|
222 |
-
$this->addFilter($filterOutRow);
|
223 |
-
|
224 |
-
return $keys;
|
225 |
-
}
|
226 |
-
|
227 |
-
/**
|
228 |
-
* Validates the array to be used by the fetchAssoc method
|
229 |
-
*
|
230 |
-
* @param array $keys
|
231 |
-
*
|
232 |
-
* @throws InvalidArgumentException If the submitted array fails the assertion
|
233 |
-
*/
|
234 |
-
protected function validateAssocKeys(array $keys)
|
235 |
-
{
|
236 |
-
if (empty($keys)) {
|
237 |
-
throw new InvalidArgumentException('The array can not be empty');
|
238 |
-
}
|
239 |
-
|
240 |
-
foreach ($keys as &$str) {
|
241 |
-
$str = $this->validateString($str);
|
242 |
-
}
|
243 |
-
unset($str);
|
244 |
-
|
245 |
-
if ($keys == array_unique($keys)) {
|
246 |
-
return $keys;
|
247 |
-
}
|
248 |
-
|
249 |
-
throw new InvalidArgumentException('The array must contain unique values');
|
250 |
-
}
|
251 |
-
|
252 |
-
/**
|
253 |
-
* Returns a single row from the CSV without filtering
|
254 |
-
*
|
255 |
-
* @param int $offset
|
256 |
-
*
|
257 |
-
* @throws InvalidArgumentException If the $offset is not valid or the row does not exist
|
258 |
-
*
|
259 |
-
* @return array
|
260 |
-
*/
|
261 |
-
protected function getRow($offset)
|
262 |
-
{
|
263 |
-
$csv = $this->getIterator();
|
264 |
-
$csv->setFlags($this->getFlags() & ~SplFileObject::READ_CSV);
|
265 |
-
$iterator = new LimitIterator($csv, $offset, 1);
|
266 |
-
$iterator->rewind();
|
267 |
-
$res = $iterator->current();
|
268 |
-
|
269 |
-
if (empty($res)) {
|
270 |
-
throw new InvalidArgumentException('the specified row does not exist or is empty');
|
271 |
-
}
|
272 |
-
|
273 |
-
if (0 == $offset && $this->isBomStrippable()) {
|
274 |
-
$res = mb_substr($res, mb_strlen($this->getInputBom()));
|
275 |
-
}
|
276 |
-
|
277 |
-
return str_getcsv($res, $this->delimiter, $this->enclosure, $this->escape);
|
278 |
-
}
|
279 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/csv/src/Writer.php
DELETED
@@ -1,164 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* This file is part of the League.csv library
|
4 |
-
*
|
5 |
-
* @license http://opensource.org/licenses/MIT
|
6 |
-
* @link https://github.com/thephpleague/csv/
|
7 |
-
* @version 7.2.0
|
8 |
-
* @package League.csv
|
9 |
-
*
|
10 |
-
* For the full copyright and license information, please view the LICENSE
|
11 |
-
* file that was distributed with this source code.
|
12 |
-
*/
|
13 |
-
namespace League\Csv;
|
14 |
-
|
15 |
-
use InvalidArgumentException;
|
16 |
-
use League\Csv\Modifier\RowFilter;
|
17 |
-
use ReflectionMethod;
|
18 |
-
use Traversable;
|
19 |
-
|
20 |
-
/**
|
21 |
-
* A class to manage data insertion into a CSV
|
22 |
-
*
|
23 |
-
* @package League.csv
|
24 |
-
* @since 4.0.0
|
25 |
-
*
|
26 |
-
*/
|
27 |
-
class Writer extends AbstractCsv
|
28 |
-
{
|
29 |
-
use RowFilter;
|
30 |
-
|
31 |
-
/**
|
32 |
-
* @inheritdoc
|
33 |
-
*/
|
34 |
-
protected $stream_filter_mode = STREAM_FILTER_WRITE;
|
35 |
-
|
36 |
-
/**
|
37 |
-
* The CSV object holder
|
38 |
-
*
|
39 |
-
* @var \SplFileObject
|
40 |
-
*/
|
41 |
-
protected $csv;
|
42 |
-
|
43 |
-
/**
|
44 |
-
* fputcsv method from SplFileObject
|
45 |
-
*
|
46 |
-
* @var ReflectionMethod
|
47 |
-
*/
|
48 |
-
protected static $fputcsv;
|
49 |
-
|
50 |
-
/**
|
51 |
-
* Nb parameters for SplFileObject::fputcsv method
|
52 |
-
*
|
53 |
-
* @var integer
|
54 |
-
*/
|
55 |
-
protected static $fputcsv_param_count;
|
56 |
-
|
57 |
-
/**
|
58 |
-
* @inheritdoc
|
59 |
-
*/
|
60 |
-
protected function __construct($path, $open_mode = 'r+')
|
61 |
-
{
|
62 |
-
parent::__construct($path, $open_mode);
|
63 |
-
static::initFputcsv();
|
64 |
-
}
|
65 |
-
|
66 |
-
/**
|
67 |
-
* initiate a SplFileObject::fputcsv method
|
68 |
-
*/
|
69 |
-
protected static function initFputcsv()
|
70 |
-
{
|
71 |
-
if (is_null(static::$fputcsv)) {
|
72 |
-
static::$fputcsv = new ReflectionMethod('\SplFileObject', 'fputcsv');
|
73 |
-
static::$fputcsv_param_count = static::$fputcsv->getNumberOfParameters();
|
74 |
-
}
|
75 |
-
}
|
76 |
-
|
77 |
-
/**
|
78 |
-
* Adds multiple lines to the CSV document
|
79 |
-
*
|
80 |
-
* a simple wrapper method around insertOne
|
81 |
-
*
|
82 |
-
* @param Traversable|array $rows a multidimensional array or a Traversable object
|
83 |
-
*
|
84 |
-
* @throws InvalidArgumentException If the given rows format is invalid
|
85 |
-
*
|
86 |
-
* @return static
|
87 |
-
*/
|
88 |
-
public function insertAll($rows)
|
89 |
-
{
|
90 |
-
if (!is_array($rows) && !$rows instanceof Traversable) {
|
91 |
-
throw new InvalidArgumentException(
|
92 |
-
'the provided data must be an array OR a \Traversable object'
|
93 |
-
);
|
94 |
-
}
|
95 |
-
|
96 |
-
foreach ($rows as $row) {
|
97 |
-
$this->insertOne($row);
|
98 |
-
}
|
99 |
-
|
100 |
-
return $this;
|
101 |
-
}
|
102 |
-
|
103 |
-
/**
|
104 |
-
* Adds a single line to a CSV document
|
105 |
-
*
|
106 |
-
* @param string[]|string $row a string, an array or an object implementing to '__toString' method
|
107 |
-
*
|
108 |
-
* @return static
|
109 |
-
*/
|
110 |
-
public function insertOne($row)
|
111 |
-
{
|
112 |
-
if (!is_array($row)) {
|
113 |
-
$row = str_getcsv($row, $this->delimiter, $this->enclosure, $this->escape);
|
114 |
-
}
|
115 |
-
$row = $this->formatRow($row);
|
116 |
-
$this->validateRow($row);
|
117 |
-
|
118 |
-
if (is_null($this->csv)) {
|
119 |
-
$this->csv = $this->getIterator();
|
120 |
-
}
|
121 |
-
|
122 |
-
static::$fputcsv->invokeArgs($this->csv, $this->getFputcsvParameters($row));
|
123 |
-
if ("\n" !== $this->newline) {
|
124 |
-
$this->csv->fseek(-1, SEEK_CUR);
|
125 |
-
$this->csv->fwrite($this->newline);
|
126 |
-
}
|
127 |
-
|
128 |
-
return $this;
|
129 |
-
}
|
130 |
-
|
131 |
-
/**
|
132 |
-
* returns the parameters for SplFileObject::fputcsv
|
133 |
-
*
|
134 |
-
* @param array $fields The fields to be add
|
135 |
-
*
|
136 |
-
* @return array
|
137 |
-
*/
|
138 |
-
protected function getFputcsvParameters(array $fields)
|
139 |
-
{
|
140 |
-
$parameters = [$fields, $this->delimiter, $this->enclosure];
|
141 |
-
if (4 == static::$fputcsv_param_count) {
|
142 |
-
$parameters[] = $this->escape;
|
143 |
-
}
|
144 |
-
|
145 |
-
return $parameters;
|
146 |
-
}
|
147 |
-
|
148 |
-
/**
|
149 |
-
* {@inheritdoc}
|
150 |
-
*/
|
151 |
-
public function isActiveStreamFilter()
|
152 |
-
{
|
153 |
-
return parent::isActiveStreamFilter() && is_null($this->csv);
|
154 |
-
}
|
155 |
-
|
156 |
-
/**
|
157 |
-
* {@inheritdoc}
|
158 |
-
*/
|
159 |
-
public function __destruct()
|
160 |
-
{
|
161 |
-
$this->csv = null;
|
162 |
-
parent::__destruct();
|
163 |
-
}
|
164 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/Services/fluentvalidator/fluentvalidator.php
CHANGED
@@ -11,11 +11,11 @@ License: GPLv2 or later
|
|
11 |
Text Domain: fluentvalidator
|
12 |
Domain Path: /resources/languages
|
13 |
*/
|
14 |
-
|
|
|
15 |
|
16 |
if (! function_exists('fluentValidator')) {
|
17 |
-
function fluentValidator($data = [], $rules = [], $messages = [])
|
18 |
-
{
|
19 |
return (new \FluentValidator\Validator($data, $rules, $messages));
|
20 |
}
|
21 |
}
|
11 |
Text Domain: fluentvalidator
|
12 |
Domain Path: /resources/languages
|
13 |
*/
|
14 |
+
|
15 |
+
require_once 'autoload.php';
|
16 |
|
17 |
if (! function_exists('fluentValidator')) {
|
18 |
+
function fluentValidator($data = [], $rules = [], $messages = []) {
|
|
|
19 |
return (new \FluentValidator\Validator($data, $rules, $messages));
|
20 |
}
|
21 |
}
|
app/Services/fluentvalidator/src/Contracts/File.php
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace FluentValidator\Contracts;
|
4 |
+
|
5 |
+
interface File
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Returns whether the file was uploaded successfully.
|
9 |
+
*
|
10 |
+
* @return bool
|
11 |
+
*/
|
12 |
+
public function isValid();
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Gets the path without filename
|
16 |
+
*
|
17 |
+
* @return string
|
18 |
+
*/
|
19 |
+
public function getPath();
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Take an educated guess of the file's extension.
|
23 |
+
*
|
24 |
+
* @return mixed|null
|
25 |
+
*/
|
26 |
+
public function guessExtension();
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Returns the original file extension.
|
30 |
+
*
|
31 |
+
* @return string
|
32 |
+
*/
|
33 |
+
public function getClientOriginalExtension();
|
34 |
+
}
|
app/Services/fluentvalidator/src/ValidatesAttributes.php
CHANGED
@@ -4,7 +4,7 @@ namespace FluentValidator;
|
|
4 |
|
5 |
use Countable;
|
6 |
use InvalidArgumentException;
|
7 |
-
use
|
8 |
|
9 |
trait ValidatesAttributes
|
10 |
{
|
4 |
|
5 |
use Countable;
|
6 |
use InvalidArgumentException;
|
7 |
+
use FluentValidator\Contracts\File;
|
8 |
|
9 |
trait ValidatesAttributes
|
10 |
{
|
app/Services/fluentvalidator/src/ValidationData.php
CHANGED
@@ -27,7 +27,7 @@ class ValidationData
|
|
27 |
|
28 |
$data = static::extractDataFromPath($explicitPath, $masterData);
|
29 |
|
30 |
-
if (!
|
31 |
return $data;
|
32 |
}
|
33 |
|
27 |
|
28 |
$data = static::extractDataFromPath($explicitPath, $masterData);
|
29 |
|
30 |
+
if (! fluentform_mb_strpos($attribute, '*') !== false || substr($attribute, -1) === '*') {
|
31 |
return $data;
|
32 |
}
|
33 |
|
app/Services/fluentvalidator/src/ValidationRuleParser.php
CHANGED
@@ -45,7 +45,7 @@ class ValidationRuleParser
|
|
45 |
protected function explodeRules($rules)
|
46 |
{
|
47 |
foreach ($rules as $attribute => $rule) {
|
48 |
-
if (
|
49 |
$rules = $this->explodeWildcardRules($rules, $attribute, [$rule]);
|
50 |
|
51 |
unset($rules[$attribute]);
|
45 |
protected function explodeRules($rules)
|
46 |
{
|
47 |
foreach ($rules as $attribute => $rule) {
|
48 |
+
if (fluentform_mb_strpos($attribute, '*') !== false) {
|
49 |
$rules = $this->explodeWildcardRules($rules, $attribute, [$rule]);
|
50 |
|
51 |
unset($rules[$attribute]);
|
config/app.php
CHANGED
@@ -6,10 +6,12 @@ return array(
|
|
6 |
'core' => array(
|
7 |
'FluentForm\Framework\Foundation\AppProvider',
|
8 |
'FluentForm\Framework\Config\ConfigProvider',
|
|
|
9 |
'FluentForm\Framework\Request\RequestProvider',
|
10 |
'FluentForm\Framework\View\ViewProvider',
|
11 |
),
|
12 |
'plugin' => array(
|
|
|
13 |
'common' => array(
|
14 |
'FluentForm\App\Providers\CommonProvider',
|
15 |
'FluentForm\App\Providers\FormBuilderProvider',
|
@@ -19,8 +21,6 @@ return array(
|
|
19 |
'backend' => array(
|
20 |
'FluentForm\App\Providers\BackendProvider',
|
21 |
'FluentForm\App\Providers\MenuProvider',
|
22 |
-
'FluentForm\App\Providers\FluentValidatorProvider',
|
23 |
-
'FluentForm\App\Providers\CsvProvider',
|
24 |
'FluentForm\App\Providers\AdminNoticeProvider'
|
25 |
),
|
26 |
|
6 |
'core' => array(
|
7 |
'FluentForm\Framework\Foundation\AppProvider',
|
8 |
'FluentForm\Framework\Config\ConfigProvider',
|
9 |
+
'FluentForm\App\Providers\FluentValidatorProvider',
|
10 |
'FluentForm\Framework\Request\RequestProvider',
|
11 |
'FluentForm\Framework\View\ViewProvider',
|
12 |
),
|
13 |
'plugin' => array(
|
14 |
+
|
15 |
'common' => array(
|
16 |
'FluentForm\App\Providers\CommonProvider',
|
17 |
'FluentForm\App\Providers\FormBuilderProvider',
|
21 |
'backend' => array(
|
22 |
'FluentForm\App\Providers\BackendProvider',
|
23 |
'FluentForm\App\Providers\MenuProvider',
|
|
|
|
|
24 |
'FluentForm\App\Providers\AdminNoticeProvider'
|
25 |
),
|
26 |
|
fluentform.php
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
<?php
|
2 |
/*
|
3 |
-
Plugin Name: FluentForm
|
4 |
Description: Contact Form By FluentForm is the advanced Contact form plugin with drag and drop, multi column supported form builder plugin
|
5 |
-
Version:
|
6 |
Author: WPManageNinja
|
7 |
Author URI: https://wpmanageninja.com
|
8 |
-
Plugin URI: https://
|
9 |
License: GPLv2 or later
|
10 |
Text Domain: fluentform
|
11 |
Domain Path: /resources/languages
|
@@ -15,7 +15,7 @@ defined('ABSPATH') or die;
|
|
15 |
|
16 |
defined('FLUENTFORM') or define('FLUENTFORM', true);
|
17 |
|
18 |
-
defined('FLUENTFORM_VERSION') or define('FLUENTFORM_VERSION', '
|
19 |
|
20 |
include "framework/Foundation/Bootstrap.php";
|
21 |
|
1 |
<?php
|
2 |
/*
|
3 |
+
Plugin Name: FluentForm - Best Form Plugin for WordPress
|
4 |
Description: Contact Form By FluentForm is the advanced Contact form plugin with drag and drop, multi column supported form builder plugin
|
5 |
+
Version: 2.0.0
|
6 |
Author: WPManageNinja
|
7 |
Author URI: https://wpmanageninja.com
|
8 |
+
Plugin URI: https://wpmanageninja.com
|
9 |
License: GPLv2 or later
|
10 |
Text Domain: fluentform
|
11 |
Domain Path: /resources/languages
|
15 |
|
16 |
defined('FLUENTFORM') or define('FLUENTFORM', true);
|
17 |
|
18 |
+
defined('FLUENTFORM_VERSION') or define('FLUENTFORM_VERSION', '2.0.0');
|
19 |
|
20 |
include "framework/Foundation/Bootstrap.php";
|
21 |
|
framework/Foundation/AppProvider.php
CHANGED
@@ -15,6 +15,11 @@ class AppProvider extends Provider
|
|
15 |
$this->app->bind(
|
16 |
'app', $this->app, 'App', 'FluentForm\Framework\Foundation\Application'
|
17 |
);
|
|
|
|
|
|
|
|
|
|
|
18 |
}
|
19 |
|
20 |
/**
|
@@ -22,13 +27,7 @@ class AppProvider extends Provider
|
|
22 |
* @return void
|
23 |
*/
|
24 |
public function booted()
|
25 |
-
{
|
26 |
-
// Framework is booted and ready
|
27 |
-
$this->app->booted(function($app) {
|
28 |
-
$app->load($app->appPath('Global/Common.php'));
|
29 |
-
$app->bootstrapWith($app->getCommonProviders());
|
30 |
-
});
|
31 |
-
|
32 |
// Application is booted and ready
|
33 |
$this->app->ready(function($app) {
|
34 |
$app->load($app->appPath('Hooks/Common.php'));
|
15 |
$this->app->bind(
|
16 |
'app', $this->app, 'App', 'FluentForm\Framework\Foundation\Application'
|
17 |
);
|
18 |
+
|
19 |
+
// Framework is booted and ready
|
20 |
+
$this->app->booted(function($app) {
|
21 |
+
$app->load($app->appPath('Global/Common.php'));
|
22 |
+
});
|
23 |
}
|
24 |
|
25 |
/**
|
27 |
* @return void
|
28 |
*/
|
29 |
public function booted()
|
30 |
+
{
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
// Application is booted and ready
|
32 |
$this->app->ready(function($app) {
|
33 |
$app->load($app->appPath('Hooks/Common.php'));
|
framework/Foundation/Application.php
CHANGED
@@ -38,19 +38,13 @@ class Application extends Container
|
|
38 |
* Callbacks for framework's booted event
|
39 |
* @var array
|
40 |
*/
|
41 |
-
protected $
|
42 |
|
43 |
/**
|
44 |
* Callbacks for framework's ready event
|
45 |
* @var array
|
46 |
*/
|
47 |
-
protected $
|
48 |
-
|
49 |
-
/**
|
50 |
-
* A flag to register the dynamic facade loader once
|
51 |
-
* @var boolean
|
52 |
-
*/
|
53 |
-
protected $isFacadeLoaderRegistered = false;
|
54 |
|
55 |
/**
|
56 |
* Get application version
|
@@ -95,10 +89,7 @@ class Application extends Container
|
|
95 |
$this->setAppBaseBindings();
|
96 |
$this->setExceptionHandler();
|
97 |
$this->loadApplicationTextDomain();
|
98 |
-
$this->
|
99 |
-
$this->fireCallbacks($this->engineBootedCallbacks);
|
100 |
-
$this->bootstrapWith($this->getPluginProviders());
|
101 |
-
$this->fireCallbacks($this->pluginReadyCallbacks);
|
102 |
}
|
103 |
|
104 |
/**
|
@@ -175,6 +166,21 @@ class Application extends Container
|
|
175 |
);
|
176 |
}
|
177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
/**
|
179 |
* Boot application with providers
|
180 |
* @param array $providers
|
@@ -189,12 +195,12 @@ class Application extends Container
|
|
189 |
$instance->booting();
|
190 |
}
|
191 |
|
192 |
-
|
193 |
-
$this->registerAppFacadeLoader();
|
194 |
-
}
|
195 |
|
196 |
-
|
197 |
-
|
|
|
|
|
198 |
}
|
199 |
}
|
200 |
|
@@ -236,8 +242,9 @@ class Application extends Container
|
|
236 |
*/
|
237 |
public function booted($callback)
|
238 |
{
|
239 |
-
$this->
|
240 |
}
|
|
|
241 |
/**
|
242 |
* Register ready events
|
243 |
* @param mixed $callback
|
@@ -245,7 +252,7 @@ class Application extends Container
|
|
245 |
*/
|
246 |
public function ready($callback)
|
247 |
{
|
248 |
-
$this->
|
249 |
}
|
250 |
|
251 |
/**
|
38 |
* Callbacks for framework's booted event
|
39 |
* @var array
|
40 |
*/
|
41 |
+
protected $booted = array();
|
42 |
|
43 |
/**
|
44 |
* Callbacks for framework's ready event
|
45 |
* @var array
|
46 |
*/
|
47 |
+
protected $ready = array();
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
/**
|
50 |
* Get application version
|
89 |
$this->setAppBaseBindings();
|
90 |
$this->setExceptionHandler();
|
91 |
$this->loadApplicationTextDomain();
|
92 |
+
$this->bootStrapApplicationProviders();
|
|
|
|
|
|
|
93 |
}
|
94 |
|
95 |
/**
|
166 |
);
|
167 |
}
|
168 |
|
169 |
+
/**
|
170 |
+
* Bootstrap all service providers
|
171 |
+
* @return void
|
172 |
+
*/
|
173 |
+
protected function bootStrapApplicationProviders()
|
174 |
+
{
|
175 |
+
$this->bootstrapWith(array_merge(
|
176 |
+
$this->getEngineProviders(),
|
177 |
+
$this->getPluginProviders(),
|
178 |
+
$this->getCommonProviders()
|
179 |
+
));
|
180 |
+
|
181 |
+
$this->fireCallbacks($this->ready);
|
182 |
+
}
|
183 |
+
|
184 |
/**
|
185 |
* Boot application with providers
|
186 |
* @param array $providers
|
195 |
$instance->booting();
|
196 |
}
|
197 |
|
198 |
+
$this->registerAppFacadeLoader();
|
|
|
|
|
199 |
|
200 |
+
$this->fireCallbacks($this->booted);
|
201 |
+
|
202 |
+
foreach ($instances as $provider) {
|
203 |
+
$provider->booted();
|
204 |
}
|
205 |
}
|
206 |
|
242 |
*/
|
243 |
public function booted($callback)
|
244 |
{
|
245 |
+
$this->booted[] = $this->parseHandler($callback);
|
246 |
}
|
247 |
+
|
248 |
/**
|
249 |
* Register ready events
|
250 |
* @param mixed $callback
|
252 |
*/
|
253 |
public function ready($callback)
|
254 |
{
|
255 |
+
$this->ready[] = $this->parseHandler($callback);
|
256 |
}
|
257 |
|
258 |
/**
|
framework/Foundation/Bootstrap.php
CHANGED
@@ -45,16 +45,7 @@ class Bootstrap
|
|
45 |
public static function init($file)
|
46 |
{
|
47 |
static::$file = $file;
|
48 |
-
|
49 |
static::$basePath = plugin_dir_path($file);
|
50 |
-
|
51 |
-
if (file_exists($activator = static::$basePath.'app/Modules/Activator.php')) {
|
52 |
-
include_once $activator;
|
53 |
-
}
|
54 |
-
|
55 |
-
if (file_exists($deactivator = static::$basePath.'app/Modules/Deactivator.php')) {
|
56 |
-
include_once $deactivator;
|
57 |
-
}
|
58 |
}
|
59 |
|
60 |
/**
|
@@ -64,7 +55,6 @@ class Bootstrap
|
|
64 |
public static function registerHooks()
|
65 |
{
|
66 |
static::registerActivationHook();
|
67 |
-
static::registerDeactivationHook();
|
68 |
}
|
69 |
|
70 |
/**
|
@@ -76,40 +66,32 @@ class Bootstrap
|
|
76 |
return register_activation_hook(
|
77 |
static::$file, array(__CLASS__, 'activate')
|
78 |
);
|
79 |
-
}
|
80 |
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
}
|
91 |
|
|
|
92 |
/**
|
93 |
* Validate and activate the plugin
|
94 |
* @return void
|
95 |
*/
|
96 |
-
public static function activate()
|
97 |
{
|
98 |
static::validatePlugin();
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
}
|
103 |
|
104 |
-
|
105 |
-
|
106 |
-
* @return void
|
107 |
-
*/
|
108 |
-
public static function deactivate()
|
109 |
-
{
|
110 |
-
// Framework specific implementation if necessary...
|
111 |
-
if (class_exists('FluentForm\App\Modules\Deactivator')) {
|
112 |
-
(new Deactivator)->handleDeactivation(static::$file);
|
113 |
}
|
114 |
}
|
115 |
|
45 |
public static function init($file)
|
46 |
{
|
47 |
static::$file = $file;
|
|
|
48 |
static::$basePath = plugin_dir_path($file);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
}
|
50 |
|
51 |
/**
|
55 |
public static function registerHooks()
|
56 |
{
|
57 |
static::registerActivationHook();
|
|
|
58 |
}
|
59 |
|
60 |
/**
|
66 |
return register_activation_hook(
|
67 |
static::$file, array(__CLASS__, 'activate')
|
68 |
);
|
|
|
69 |
|
70 |
+
// Handle Newtwork new Site Activation
|
71 |
+
$basePath = static::$basePath;
|
72 |
+
add_action( 'wpmu_new_blog', function ($blogId) use ($basePath) {
|
73 |
+
switch_to_blog( $blogId );
|
74 |
+
include_once $basePath.'app/Modules/Activator.php';
|
75 |
+
(new Activator)->migrate();
|
76 |
+
restore_current_blog();
|
77 |
+
} );
|
78 |
+
|
79 |
}
|
80 |
|
81 |
+
|
82 |
/**
|
83 |
* Validate and activate the plugin
|
84 |
* @return void
|
85 |
*/
|
86 |
+
public static function activate($netowrkwide = false)
|
87 |
{
|
88 |
static::validatePlugin();
|
89 |
+
if (file_exists($activator = static::$basePath.'app/Modules/Activator.php')) {
|
90 |
+
include_once $activator;
|
91 |
+
}
|
|
|
92 |
|
93 |
+
if (class_exists('FluentForm\App\Modules\Activator')) {
|
94 |
+
(new Activator)->handleActivation($netowrkwide);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
}
|
96 |
}
|
97 |
|
framework/Request/File.php
CHANGED
@@ -2,7 +2,9 @@
|
|
2 |
|
3 |
namespace FluentForm\Framework\Request;
|
4 |
|
5 |
-
|
|
|
|
|
6 |
{
|
7 |
/**
|
8 |
* A map of mime types and their default extensions.
|
@@ -893,7 +895,6 @@ class File extends \SplFileInfo
|
|
893 |
public function guessExtension()
|
894 |
{
|
895 |
$type = $this->getMimeType();
|
896 |
-
|
897 |
return isset($this->defaultExtensions[$type]) ? $this->defaultExtensions[$type] : null;
|
898 |
}
|
899 |
|
@@ -905,16 +906,11 @@ class File extends \SplFileInfo
|
|
905 |
public function getMimeType()
|
906 |
{
|
907 |
$path = $this->getPathname();
|
908 |
-
|
909 |
-
|
910 |
-
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
911 |
-
|
912 |
-
$mime = $finfo->file($path);
|
913 |
-
} else {
|
914 |
-
$mime = wp_check_filetype($this->originalName)['type'];
|
915 |
}
|
916 |
-
|
917 |
-
return $
|
918 |
}
|
919 |
|
920 |
/**
|
2 |
|
3 |
namespace FluentForm\Framework\Request;
|
4 |
|
5 |
+
use FluentValidator\Contracts\File as Contract;
|
6 |
+
|
7 |
+
class File extends \SplFileInfo implements Contract
|
8 |
{
|
9 |
/**
|
10 |
* A map of mime types and their default extensions.
|
895 |
public function guessExtension()
|
896 |
{
|
897 |
$type = $this->getMimeType();
|
|
|
898 |
return isset($this->defaultExtensions[$type]) ? $this->defaultExtensions[$type] : null;
|
899 |
}
|
900 |
|
906 |
public function getMimeType()
|
907 |
{
|
908 |
$path = $this->getPathname();
|
909 |
+
if(!function_exists('wp_check_filetype_and_ext')) {
|
910 |
+
require_once ABSPATH .'wp-admin/includes/file.php';
|
|
|
|
|
|
|
|
|
|
|
911 |
}
|
912 |
+
$typeInfo = wp_check_filetype_and_ext($path, $this->originalName);
|
913 |
+
return $typeInfo['type'];
|
914 |
}
|
915 |
|
916 |
/**
|
framework/Request/RequestProvider.php
CHANGED
@@ -6,16 +6,14 @@ use FluentForm\Framework\Foundation\Provider;
|
|
6 |
|
7 |
class RequestProvider extends Provider
|
8 |
{
|
|
|
|
|
9 |
/**
|
10 |
* The provider booting method to boot this provider
|
11 |
* @return void
|
12 |
*/
|
13 |
-
public function
|
14 |
{
|
15 |
-
// $this->app->bindSingleton('request', function($app) {
|
16 |
-
// return new Request($app, $_GET, $_POST, $_FILES);
|
17 |
-
// }, 'Request', 'FluentForm\Framework\Request\Request');
|
18 |
-
|
19 |
$this->app->bindInstance(
|
20 |
'request',
|
21 |
new Request($this->app, $_GET, $_POST, $_FILES),
|
6 |
|
7 |
class RequestProvider extends Provider
|
8 |
{
|
9 |
+
public function booting() {}
|
10 |
+
|
11 |
/**
|
12 |
* The provider booting method to boot this provider
|
13 |
* @return void
|
14 |
*/
|
15 |
+
public function booted()
|
16 |
{
|
|
|
|
|
|
|
|
|
17 |
$this->app->bindInstance(
|
18 |
'request',
|
19 |
new Request($this->app, $_GET, $_POST, $_FILES),
|
glue.json
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
"plugin_name": "FluentForm",
|
3 |
"plugin_slug": "fluentform",
|
4 |
"plugin_text_domain": "fluentform",
|
5 |
-
"plugin_version": "
|
6 |
"plugin_description": "The most advanced drag and drop form builder plugin for WordPress",
|
7 |
"plugin_uri": "https://wpfluentform.com",
|
8 |
"plugin_license": "GPLv2 or later",
|
2 |
"plugin_name": "FluentForm",
|
3 |
"plugin_slug": "fluentform",
|
4 |
"plugin_text_domain": "fluentform",
|
5 |
+
"plugin_version": "2.0.0",
|
6 |
"plugin_description": "The most advanced drag and drop form builder plugin for WordPress",
|
7 |
"plugin_uri": "https://wpfluentform.com",
|
8 |
"plugin_license": "GPLv2 or later",
|
public/css/add-ons.css
CHANGED
@@ -1 +1 @@
|
|
1 |
-
.ff-add-ons{display:block;width:100%;position:relative;margin-bottom:20px;overflow:hidden}.ff-add-on{width:33.33%;float:left;padding-right:30px;margin-bottom:10px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden}@media (max-width:768px){.ff-add-on{width:100%;float:none;padding-right:0;margin-bottom:30px}}.ff-add-on.ff-featured-addon{width:100%}.ff-add-on.ff-featured-addon a.button.button-primary{background:#0084ff;font-size:18px;padding:5px 20px;height:auto}.ff-add-on.ff-featured-addon p.ff-add-on-active{position:static;max-width:250px}.ff-add-on.ff-featured-addon .ff-plugin_info{float:left;max-width:250px;padding-right:20px;border-right:1px solid #dcd8d8}.ff-add-on.ff-featured-addon .ff-plugin_info>img{max-width:200px;width:100%;height:auto!important;max-height:none}.ff-add-on.ff-featured-addon .ff-add-on-description{margin-bottom:20px;font-size:16px}.ff-add-on.ff-featured-addon .ff-featured_content{float:left;padding-left:20px;display:block}.ff-add-on.ff-featured-addon .ff-featured_content .ff-integration{max-width:300px;height:auto}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists{text-align:left;float:left;margin-right:150px}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists:last-child{margin-right:0}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists ul li{font-size:17px;margin-bottom:10px}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists ul li span{color:green;font-weight:700}.ff-add-on-box{padding:15px;background:#fff;-webkit-box-shadow:0 0 5px rgba(0,0,0,.05);box-shadow:0 0 5px rgba(0,0,0,.05);border-radius:4px;min-height:280px;position:relative;overflow:hidden}.ff-add-on-box img{max-width:100px;max-height:100px}.text-center{text-align:center}.ff-add-on-active{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.ff-add-on-inactive{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.ff-add-on-active,.ff-add-on-inactive{font-weight:700;padding:6px;position:absolute;left:15px;right:15px;bottom:15px}.ff-add-on-description{margin-bottom:56px}.ff_add_on_navigation{display:block;width:100%}.ff_add_on_navigation ul{list-style:none;padding:0;margin:0;background:#fff}.ff_add_on_navigation ul li{display:inline-block;margin-bottom:0}.ff_add_on_navigation ul li a{padding:10px 15px 12px;display:block;text-decoration:none;font-weight:700;border-right:1px solid #f1f1f1;border-top:2px solid transparent}.ff_add_on_navigation li.ff_add_on_item.ff_menu_item_active a{border-top:2px solid #333;color:#333}.fluent_activation_wrapper{background:#fbf8f8;padding:45px 20px;display:block;position:relative;margin-top:20px;text-align:center}.fluent_activate_now{display:none}label.fluentform_label{display:block;padding:10px;margin-top:30px}label.fluentform_label span{display:block;font-size:20px;margin-bottom:10px}label.fluentform_label input{height:45px;font-size:22px;padding:5px 15px;border-radius:5px}p.contact_us_line{margin-top:75px;font-size:13px;color:#777575}.fluent_activation_wrapper .button-primary.button_activate{font-size:19px!important;padding:5px 25px!important;height:auto!important}.license_activated_sucess{padding:20px 0;font-size:20px;color:#525556}.fluent_activation_wrapper .fluent_plugin_activated_hide{display:none!important}
|
1 |
+
.ff_form_wrap{margin:0}.ff_form_wrap *{-webkit-box-sizing:border-box;box-sizing:border-box}.ff_form_wrap .ff_form_wrap_area{padding:15px 15px 15px 0;overflow:hidden;display:block}.ff_form_wrap .ff_form_wrap_area>h2{margin-top:0}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{-webkit-appearance:none;background-color:#fff;border-radius:4px;border:1px solid #dcdfe6;color:#606266;-webkit-box-shadow:none;box-shadow:none;margin:0;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,textarea:focus{-webkit-box-shadow:none;box-shadow:none}input[type=color].el-select__input,input[type=date].el-select__input,input[type=datetime-local].el-select__input,input[type=datetime].el-select__input,input[type=email].el-select__input,input[type=month].el-select__input,input[type=number].el-select__input,input[type=password].el-select__input,input[type=search].el-select__input,input[type=tel].el-select__input,input[type=text].el-select__input,input[type=time].el-select__input,input[type=url].el-select__input,input[type=week].el-select__input,textarea.el-select__input{border:none;background-color:transparent}p{margin-top:0;margin-bottom:10px}.icon{font:normal normal normal 14px/1 ultimateform;display:inline-block}.mr15{margin-right:15px}.mb15{margin-bottom:15px}.pull-left{float:left!important}.pull-right{float:right!important}.text-left{text-align:left}.text-right{text-align:right}.el-icon-clickable{cursor:pointer}.help-text{margin:0;font-style:italic;font-size:.9em}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:500;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-block{width:100%}.clearfix:after,.clearfix:before,.form-editor:after,.form-editor:before{display:table;content:" "}.clearfix:after,.form-editor:after{clear:both}.el-notification__content p{text-align:left}.label-lh-1-5 label{line-height:1.5}.ff_form_main_nav{display:block;width:auto;overflow:hidden;border-bottom:1px solid #ddd;background:#fff;margin:0 0 0 -20px;padding:10px 20px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.ff_form_main_nav span.plugin-name{font-size:16px;color:#6e6e6e;margin-right:30px}.ff_form_main_nav .ninja-tab{padding:10px;display:inline-block;text-decoration:none;color:#000;font-size:15px;line-height:16px;margin-right:15px;border-bottom:2px solid transparent}.ff_form_main_nav .ninja-tab:focus{-webkit-box-shadow:none;box-shadow:none}.ff_form_main_nav .ninja-tab.ninja-tab-active{font-weight:700;border-bottom:2px solid #ffd65b}.ff_form_main_nav .ninja-tab.buy_pro_tab{background:#e04f5e;padding:10px 25px;color:#fff}.el-dialog__wrapper .el-dialog{background:transparent}.el-dialog__wrapper .el-dialog__header{display:block;overflow:hidden;background:#f5f5f5;border:1px solid #ddd;padding:10px;border-top-left-radius:5px;border-top-right-radius:5px}.el-dialog__wrapper .el-dialog__header .el-dialog__headerbtn{top:12px}.el-dialog__wrapper .el-dialog__footer{background:#f5f5f5;border-bottom-left-radius:5px;border-bottom-right-radius:5px}.el-dialog__wrapper .el-dialog__body{padding:20px;background:#fff;border-radius:0}.el-dialog__wrapper .el-dialog__body .dialog-footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box;background:#f5f5f5;border-bottom-left-radius:5px;border-bottom-right-radius:5px;width:auto;display:block;margin:0 -20px -20px}.ff_nav_top{overflow:hidden;display:block;margin-bottom:15px;width:100%}.ff_nav_top .ff_nav_title{float:left;width:50%}.ff_nav_top .ff_nav_title h3{float:left;margin:0;padding:0;line-height:28px}.ff_nav_top .ff_nav_title .ff_nav_sub_actions{display:inline-block;margin-left:20px}.ff_nav_top .ff_nav_action{float:left;width:50%;text-align:right}.ff_nav_top .ff_search_inline{width:200px;text-align:right;display:inline-block}.ff-add-ons{display:block;width:100%;position:relative;margin-bottom:20px;overflow:hidden}.ff-add-on{width:33.33%;float:left;padding-right:30px;margin-bottom:10px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden}@media (max-width:768px){.ff-add-on{width:100%;float:none;padding-right:0;margin-bottom:30px}}.ff-add-on.ff-featured-addon{width:100%}.ff-add-on.ff-featured-addon a.button.button-primary{background:#0084ff;font-size:18px;padding:5px 20px;height:auto}.ff-add-on.ff-featured-addon p.ff-add-on-active{position:static;max-width:250px}.ff-add-on.ff-featured-addon .ff-plugin_info{float:left;max-width:250px;padding-right:20px;border-right:1px solid #dcd8d8}.ff-add-on.ff-featured-addon .ff-plugin_info>img{max-width:200px;width:100%;height:auto!important;max-height:none}.ff-add-on.ff-featured-addon .ff-add-on-description{margin-bottom:20px;font-size:16px}.ff-add-on.ff-featured-addon .ff-featured_content{float:left;padding-left:20px;display:block}.ff-add-on.ff-featured-addon .ff-featured_content .ff-integration{max-width:300px;height:auto}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists{text-align:left;float:left;margin-right:150px}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists:last-child{margin-right:0}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists ul li{font-size:17px;margin-bottom:10px}.ff-add-on.ff-featured-addon .ff-featured_content .ff-feature-lists ul li span{color:green;font-weight:700}.ff-add-on-box{padding:15px;background:#fff;-webkit-box-shadow:0 0 5px rgba(0,0,0,.05);box-shadow:0 0 5px rgba(0,0,0,.05);border-radius:4px;min-height:280px;position:relative;overflow:hidden}.ff-add-on-box img{max-width:100px;max-height:100px}.text-center{text-align:center}.ff-add-on-active{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.ff-add-on-inactive{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.ff-add-on-active,.ff-add-on-inactive{font-weight:700;padding:6px;position:absolute;left:15px;right:15px;bottom:15px}.ff-add-on-description{margin-bottom:56px}.ff_add_on_navigation{display:block;width:100%}.ff_add_on_navigation ul{list-style:none;padding:0;margin:0;background:#fff}.ff_add_on_navigation ul li{display:inline-block;margin-bottom:0}.ff_add_on_navigation ul li a{padding:10px 15px 12px;display:block;text-decoration:none;font-weight:700;border-right:1px solid #f1f1f1;border-top:2px solid transparent}.ff_add_on_navigation li.ff_add_on_item.ff_menu_item_active a{border-top:2px solid #333;color:#333}.fluent_activation_wrapper{background:#fbf8f8;padding:45px 20px;display:block;position:relative;margin-top:20px;text-align:center}.fluent_activate_now{display:none}label.fluentform_label{display:block;padding:10px;margin-top:30px}label.fluentform_label span{display:block;font-size:20px;margin-bottom:10px}label.fluentform_label input{height:45px;font-size:22px;padding:5px 15px;border-radius:5px}p.contact_us_line{margin-top:75px;font-size:13px;color:#777575}.fluent_activation_wrapper .button-primary.button_activate{font-size:19px!important;padding:5px 25px!important;height:auto!important}.license_activated_sucess{padding:20px 0;font-size:20px;color:#525556}.fluent_activation_wrapper .fluent_plugin_activated_hide{display:none!important}
|
public/css/admin_docs.css
CHANGED
@@ -1 +1 @@
|
|
1 |
-
.ff_doc_top_blocks{display:block;width:100%;position:relative;margin-bottom:20px;overflow:hidden}.ff_doc_top_blocks>*{-webkit-box-sizing:border-box;box-sizing:border-box}.ff_doc_top_blocks .block_1_3{width:33.33%;float:left;padding-right:30px;margin-bottom:30px}@media (max-width:768px){.ff_doc_top_blocks .block_1_3{width:100%;float:none;padding-right:0;margin-bottom:30px}}.ff_doc_top_blocks .ff_block .ff_block_box{padding:15px;background:#fff;-webkit-box-shadow:0 0 5px rgba(0,0,0,.05);box-shadow:0 0 5px rgba(0,0,0,.05);border-radius:4px}.ff_doc_top_blocks .ff_block .ff_block_box ul{list-style:disc;margin-left:20px}.ff_doc_top_blocks .ff_block .ff_block_box ul li a{font-size:14px;text-decoration:none;line-height:22px}.text-center{text-align:center}
|
1 |
+
.ff_form_wrap{margin:0}.ff_form_wrap *{-webkit-box-sizing:border-box;box-sizing:border-box}.ff_form_wrap .ff_form_wrap_area{padding:15px 15px 15px 0;overflow:hidden;display:block}.ff_form_wrap .ff_form_wrap_area>h2{margin-top:0}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{-webkit-appearance:none;background-color:#fff;border-radius:4px;border:1px solid #dcdfe6;color:#606266;-webkit-box-shadow:none;box-shadow:none;margin:0;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,textarea:focus{-webkit-box-shadow:none;box-shadow:none}input[type=color].el-select__input,input[type=date].el-select__input,input[type=datetime-local].el-select__input,input[type=datetime].el-select__input,input[type=email].el-select__input,input[type=month].el-select__input,input[type=number].el-select__input,input[type=password].el-select__input,input[type=search].el-select__input,input[type=tel].el-select__input,input[type=text].el-select__input,input[type=time].el-select__input,input[type=url].el-select__input,input[type=week].el-select__input,textarea.el-select__input{border:none;background-color:transparent}p{margin-top:0;margin-bottom:10px}.icon{font:normal normal normal 14px/1 ultimateform;display:inline-block}.mr15{margin-right:15px}.mb15{margin-bottom:15px}.pull-left{float:left!important}.pull-right{float:right!important}.text-left{text-align:left}.text-right{text-align:right}.el-icon-clickable{cursor:pointer}.help-text{margin:0;font-style:italic;font-size:.9em}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:500;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-block{width:100%}.clearfix:after,.clearfix:before,.form-editor:after,.form-editor:before{display:table;content:" "}.clearfix:after,.form-editor:after{clear:both}.el-notification__content p{text-align:left}.label-lh-1-5 label{line-height:1.5}.ff_form_main_nav{display:block;width:auto;overflow:hidden;border-bottom:1px solid #ddd;background:#fff;margin:0 0 0 -20px;padding:10px 20px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.ff_form_main_nav span.plugin-name{font-size:16px;color:#6e6e6e;margin-right:30px}.ff_form_main_nav .ninja-tab{padding:10px;display:inline-block;text-decoration:none;color:#000;font-size:15px;line-height:16px;margin-right:15px;border-bottom:2px solid transparent}.ff_form_main_nav .ninja-tab:focus{-webkit-box-shadow:none;box-shadow:none}.ff_form_main_nav .ninja-tab.ninja-tab-active{font-weight:700;border-bottom:2px solid #ffd65b}.ff_form_main_nav .ninja-tab.buy_pro_tab{background:#e04f5e;padding:10px 25px;color:#fff}.el-dialog__wrapper .el-dialog{background:transparent}.el-dialog__wrapper .el-dialog__header{display:block;overflow:hidden;background:#f5f5f5;border:1px solid #ddd;padding:10px;border-top-left-radius:5px;border-top-right-radius:5px}.el-dialog__wrapper .el-dialog__header .el-dialog__headerbtn{top:12px}.el-dialog__wrapper .el-dialog__footer{background:#f5f5f5;border-bottom-left-radius:5px;border-bottom-right-radius:5px}.el-dialog__wrapper .el-dialog__body{padding:20px;background:#fff;border-radius:0}.el-dialog__wrapper .el-dialog__body .dialog-footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box;background:#f5f5f5;border-bottom-left-radius:5px;border-bottom-right-radius:5px;width:auto;display:block;margin:0 -20px -20px}.ff_nav_top{overflow:hidden;display:block;margin-bottom:15px;width:100%}.ff_nav_top .ff_nav_title{float:left;width:50%}.ff_nav_top .ff_nav_title h3{float:left;margin:0;padding:0;line-height:28px}.ff_nav_top .ff_nav_title .ff_nav_sub_actions{display:inline-block;margin-left:20px}.ff_nav_top .ff_nav_action{float:left;width:50%;text-align:right}.ff_nav_top .ff_search_inline{width:200px;text-align:right;display:inline-block}.ff_doc_top_blocks{display:block;width:100%;position:relative;margin-bottom:20px;overflow:hidden}.ff_doc_top_blocks>*{-webkit-box-sizing:border-box;box-sizing:border-box}.ff_doc_top_blocks .block_1_3{width:33.33%;float:left;padding-right:30px;margin-bottom:30px}@media (max-width:768px){.ff_doc_top_blocks .block_1_3{width:100%;float:none;padding-right:0;margin-bottom:30px}}.ff_doc_top_blocks .ff_block .ff_block_box{padding:15px;background:#fff;-webkit-box-shadow:0 0 5px rgba(0,0,0,.05);box-shadow:0 0 5px rgba(0,0,0,.05);border-radius:4px}.ff_doc_top_blocks .ff_block .ff_block_box ul{list-style:disc;margin-left:20px}.ff_doc_top_blocks .ff_block .ff_block_box ul li a{font-size:14px;text-decoration:none;line-height:22px}.text-center{text-align:center}
|
public/css/fluent-all-forms.css
CHANGED
@@ -1 +1 @@
|
|
1 |
-
@font-face{font-family:element-icons;src:url(../fonts/element-icons.woff?2fad952a20fbbcfd1bf2ebb210dccf7a) format("woff"),url(../fonts/element-icons.ttf?6f0a76321d30f3c8120915e57f7bd77e) format("truetype");font-weight:400;font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-upload:before{content:"\E60D"}.el-icon-error:before{content:"\E62C"}.el-icon-success:before{content:"\E62D"}.el-icon-warning:before{content:"\E62E"}.el-icon-sort-down:before{content:"\E630"}.el-icon-sort-up:before{content:"\E631"}.el-icon-arrow-left:before{content:"\E600"}.el-icon-circle-plus:before{content:"\E601"}.el-icon-circle-plus-outline:before{content:"\E602"}.el-icon-arrow-down:before{content:"\E603"}.el-icon-arrow-right:before{content:"\E604"}.el-icon-arrow-up:before{content:"\E605"}.el-icon-back:before{content:"\E606"}.el-icon-circle-close:before{content:"\E607"}.el-icon-date:before{content:"\E608"}.el-icon-circle-close-outline:before{content:"\E609"}.el-icon-caret-left:before{content:"\E60A"}.el-icon-caret-bottom:before{content:"\E60B"}.el-icon-caret-top:before{content:"\E60C"}.el-icon-caret-right:before{content:"\E60E"}.el-icon-close:before{content:"\E60F"}.el-icon-d-arrow-left:before{content:"\E610"}.el-icon-check:before{content:"\E611"}.el-icon-delete:before{content:"\E612"}.el-icon-d-arrow-right:before{content:"\E613"}.el-icon-document:before{content:"\E614"}.el-icon-d-caret:before{content:"\E615"}.el-icon-edit-outline:before{content:"\E616"}.el-icon-download:before{content:"\E617"}.el-icon-goods:before{content:"\E618"}.el-icon-search:before{content:"\E619"}.el-icon-info:before{content:"\E61A"}.el-icon-message:before{content:"\E61B"}.el-icon-edit:before{content:"\E61C"}.el-icon-location:before{content:"\E61D"}.el-icon-loading:before{content:"\E61E"}.el-icon-location-outline:before{content:"\E61F"}.el-icon-menu:before{content:"\E620"}.el-icon-minus:before{content:"\E621"}.el-icon-bell:before{content:"\E622"}.el-icon-mobile-phone:before{content:"\E624"}.el-icon-news:before{content:"\E625"}.el-icon-more:before{content:"\E646"}.el-icon-more-outline:before{content:"\E626"}.el-icon-phone:before{content:"\E627"}.el-icon-phone-outline:before{content:"\E628"}.el-icon-picture:before{content:"\E629"}.el-icon-picture-outline:before{content:"\E62A"}.el-icon-plus:before{content:"\E62B"}.el-icon-printer:before{content:"\E62F"}.el-icon-rank:before{content:"\E632"}.el-icon-refresh:before{content:"\E633"}.el-icon-question:before{content:"\E634"}.el-icon-remove:before{content:"\E635"}.el-icon-share:before{content:"\E636"}.el-icon-star-on:before{content:"\E637"}.el-icon-setting:before{content:"\E638"}.el-icon-circle-check:before{content:"\E639"}.el-icon-service:before{content:"\E63A"}.el-icon-sold-out:before{content:"\E63B"}.el-icon-remove-outline:before{content:"\E63C"}.el-icon-star-off:before{content:"\E63D"}.el-icon-circle-check-outline:before{content:"\E63E"}.el-icon-tickets:before{content:"\E63F"}.el-icon-sort:before{content:"\E640"}.el-icon-zoom-in:before{content:"\E641"}.el-icon-time:before{content:"\E642"}.el-icon-view:before{content:"\E643"}.el-icon-upload2:before{content:"\E644"}.el-icon-zoom-out:before{content:"\E645"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.ff_form_wrap{margin:0;margin-left:-20px}.ff_all_forms{padding:15px}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{-webkit-appearance:none;background-color:#fff;border-radius:4px;border:1px solid #dcdfe6;color:#606266;-webkit-box-shadow:none;box-shadow:none;margin:0;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,textarea:focus{-webkit-box-shadow:none;box-shadow:none}input[type=color].el-select__input,input[type=date].el-select__input,input[type=datetime-local].el-select__input,input[type=datetime].el-select__input,input[type=email].el-select__input,input[type=month].el-select__input,input[type=number].el-select__input,input[type=password].el-select__input,input[type=search].el-select__input,input[type=tel].el-select__input,input[type=text].el-select__input,input[type=time].el-select__input,input[type=url].el-select__input,input[type=week].el-select__input,textarea.el-select__input{border:none;background-color:transparent}p{margin-top:0;margin-bottom:10px}.icon{font:normal normal normal 14px/1 ultimateform;display:inline-block}.mr15{margin-right:15px}.mb15{margin-bottom:15px}.pull-left{float:left!important}.pull-right{float:right!important}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.el-icon-clickable{cursor:pointer}.help-text{margin:0;font-style:italic;font-size:.9em}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:500;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-block{width:100%}.clearfix:after,.clearfix:before,.form-editor:after,.form-editor:before{display:table;content:" "}.clearfix:after,.form-editor:after{clear:both}.el-notification__content p{text-align:left}.label-lh-1-5 label{line-height:1.5}.el-text-primary{color:#20a0ff}.el-text-info{color:#58b7ff}.el-text-success{color:#13ce66}.el-text-warning{color:#f7ba2a}.el-text-danger{color:#ff4949}.el-notification__content{text-align:left}.el-input-group--append .el-input-group__append{left:-2px}.action-buttons .el-button+.el-button{margin-left:0}.el-box-card{-webkit-box-shadow:none;box-shadow:none}.el-box-footer,.el-box-header{background-color:#edf1f6;padding:15px}.el-box-body{padding:15px}.el-form-item__force-inline.el-form-item__label{float:left;padding:11px 12px 11px 0}.el-form-item .el-form-item{margin-bottom:10px}.el-form-item__content .line{text-align:center}.el-basic-collapse{border:0;margin-bottom:15px}.el-basic-collapse .el-collapse-item__header{padding-left:0;display:inline-block;border:0}.el-basic-collapse .el-collapse-item__wrap{border:0}.el-basic-collapse .el-collapse-item__content{padding:0;background-color:#fff}.el-collapse-settings{margin-bottom:15px}.el-collapse-settings .el-collapse-item__header{background:#f1f1f1;padding-left:20px}.el-collapse-settings .el-collapse-item__content{padding-bottom:0;margin-top:15px}.el-collapse-settings .el-collapse-item__arrow{line-height:48px}.el-popover{text-align:left}.option-fields-section--content .el-form-item{margin-bottom:10px}.option-fields-section--content .el-form-item__label{padding-bottom:5px;font-size:13px;line-height:1}.option-fields-section--content .el-input__inner{height:30px;padding:0 8px}.option-fields-section--content .el-form-item__content{line-height:1.5;margin-bottom:5px}.el-dropdown-list{border:0;margin:5px 0;-webkit-box-shadow:none;box-shadow:none;padding:0;z-index:10;position:static;min-width:auto;max-height:280px;overflow-y:scroll}.el-dropdown-list .el-dropdown-menu__item{font-size:13px;line-height:18px;padding:4px 10px;border-bottom:1px solid #f1f1f1}.el-dropdown-list .el-dropdown-menu__item:last-of-type{border-bottom:0}.el-form-nested.el-form--label-left .el-form-item__label{float:left;padding:10px 5px 10px 0}.el-message{top:40px}.el-button{text-decoration:none}.form-editor-elements:not(.el-form--label-left):not(.el-form--label-right) .el-form-item__label{line-height:1}.folded .el-dialog__wrapper{left:36px}.el-dialog__wrapper{left:160px}.ff-el-banner{width:200px;height:250px;border:1px solid #dce0e5;float:left;display:inline-block;padding:5px;-webkit-transition:border .3s;transition:border .3s}.ff-el-banner-group{overflow:hidden}.ff-el-banner+.ff-el-banner{margin-left:10px}.ff-el-banner img{width:100%;height:auto;display:block}.ff-el-banner-header{text-align:center;margin:0;background:#409eff;padding:6px;font-size:15px;color:#fff;font-weight:400}.ff-el-banner-inner-item{position:relative;overflow:hidden;height:inherit}.ff-el-banner:hover .ff-el-banner-text-inside{opacity:1;visibility:visible}.ff-el-banner-text-inside{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.ff-el-banner-text-inside-hoverable{opacity:0;visibility:hidden;position:absolute;-webkit-transition:all .3s;transition:all .3s;color:#fff;top:0;left:0;right:0;bottom:0;padding:10px;background-color:rgba(0,0,0,.6)}.ff-el-banner-text-inside .form-title{color:#fff;margin:0 0 10px}.v-modal{display:none!important}.backdrop{background:rgba(0,0,0,.5);position:fixed;top:0;left:0;right:0;bottom:0;z-index:5}.compact td>.cell,.compact th>.cell{white-space:nowrap}.ff_all_forms .pull-right{float:right}.ff_all_forms .form_navigation{margin-bottom:20px}
|
1 |
+
@font-face{font-family:element-icons;src:url(../fonts/element-icons.woff?2fad952a20fbbcfd1bf2ebb210dccf7a) format("woff"),url(../fonts/element-icons.ttf?6f0a76321d30f3c8120915e57f7bd77e) format("truetype");font-weight:400;font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-upload:before{content:"\E60D"}.el-icon-error:before{content:"\E62C"}.el-icon-success:before{content:"\E62D"}.el-icon-warning:before{content:"\E62E"}.el-icon-sort-down:before{content:"\E630"}.el-icon-sort-up:before{content:"\E631"}.el-icon-arrow-left:before{content:"\E600"}.el-icon-circle-plus:before{content:"\E601"}.el-icon-circle-plus-outline:before{content:"\E602"}.el-icon-arrow-down:before{content:"\E603"}.el-icon-arrow-right:before{content:"\E604"}.el-icon-arrow-up:before{content:"\E605"}.el-icon-back:before{content:"\E606"}.el-icon-circle-close:before{content:"\E607"}.el-icon-date:before{content:"\E608"}.el-icon-circle-close-outline:before{content:"\E609"}.el-icon-caret-left:before{content:"\E60A"}.el-icon-caret-bottom:before{content:"\E60B"}.el-icon-caret-top:before{content:"\E60C"}.el-icon-caret-right:before{content:"\E60E"}.el-icon-close:before{content:"\E60F"}.el-icon-d-arrow-left:before{content:"\E610"}.el-icon-check:before{content:"\E611"}.el-icon-delete:before{content:"\E612"}.el-icon-d-arrow-right:before{content:"\E613"}.el-icon-document:before{content:"\E614"}.el-icon-d-caret:before{content:"\E615"}.el-icon-edit-outline:before{content:"\E616"}.el-icon-download:before{content:"\E617"}.el-icon-goods:before{content:"\E618"}.el-icon-search:before{content:"\E619"}.el-icon-info:before{content:"\E61A"}.el-icon-message:before{content:"\E61B"}.el-icon-edit:before{content:"\E61C"}.el-icon-location:before{content:"\E61D"}.el-icon-loading:before{content:"\E61E"}.el-icon-location-outline:before{content:"\E61F"}.el-icon-menu:before{content:"\E620"}.el-icon-minus:before{content:"\E621"}.el-icon-bell:before{content:"\E622"}.el-icon-mobile-phone:before{content:"\E624"}.el-icon-news:before{content:"\E625"}.el-icon-more:before{content:"\E646"}.el-icon-more-outline:before{content:"\E626"}.el-icon-phone:before{content:"\E627"}.el-icon-phone-outline:before{content:"\E628"}.el-icon-picture:before{content:"\E629"}.el-icon-picture-outline:before{content:"\E62A"}.el-icon-plus:before{content:"\E62B"}.el-icon-printer:before{content:"\E62F"}.el-icon-rank:before{content:"\E632"}.el-icon-refresh:before{content:"\E633"}.el-icon-question:before{content:"\E634"}.el-icon-remove:before{content:"\E635"}.el-icon-share:before{content:"\E636"}.el-icon-star-on:before{content:"\E637"}.el-icon-setting:before{content:"\E638"}.el-icon-circle-check:before{content:"\E639"}.el-icon-service:before{content:"\E63A"}.el-icon-sold-out:before{content:"\E63B"}.el-icon-remove-outline:before{content:"\E63C"}.el-icon-star-off:before{content:"\E63D"}.el-icon-circle-check-outline:before{content:"\E63E"}.el-icon-tickets:before{content:"\E63F"}.el-icon-sort:before{content:"\E640"}.el-icon-zoom-in:before{content:"\E641"}.el-icon-time:before{content:"\E642"}.el-icon-view:before{content:"\E643"}.el-icon-upload2:before{content:"\E644"}.el-icon-zoom-out:before{content:"\E645"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.ff_form_wrap{margin:0}.ff_form_wrap *{-webkit-box-sizing:border-box;box-sizing:border-box}.ff_form_wrap .ff_form_wrap_area{padding:15px 15px 15px 0;overflow:hidden;display:block}.ff_form_wrap .ff_form_wrap_area>h2{margin-top:0}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{-webkit-appearance:none;background-color:#fff;border-radius:4px;border:1px solid #dcdfe6;color:#606266;-webkit-box-shadow:none;box-shadow:none;margin:0;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,textarea:focus{-webkit-box-shadow:none;box-shadow:none}input[type=color].el-select__input,input[type=date].el-select__input,input[type=datetime-local].el-select__input,input[type=datetime].el-select__input,input[type=email].el-select__input,input[type=month].el-select__input,input[type=number].el-select__input,input[type=password].el-select__input,input[type=search].el-select__input,input[type=tel].el-select__input,input[type=text].el-select__input,input[type=time].el-select__input,input[type=url].el-select__input,input[type=week].el-select__input,textarea.el-select__input{border:none;background-color:transparent}p{margin-top:0;margin-bottom:10px}.icon{font:normal normal normal 14px/1 ultimateform;display:inline-block}.mr15{margin-right:15px}.mb15{margin-bottom:15px}.pull-left{float:left!important}.pull-right{float:right!important}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.el-icon-clickable{cursor:pointer}.help-text{margin:0;font-style:italic;font-size:.9em}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:500;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-block{width:100%}.clearfix:after,.clearfix:before,.form-editor:after,.form-editor:before{display:table;content:" "}.clearfix:after,.form-editor:after{clear:both}.el-notification__content p{text-align:left}.label-lh-1-5 label{line-height:1.5}.ff_form_main_nav{display:block;width:auto;overflow:hidden;border-bottom:1px solid #ddd;background:#fff;margin:0 0 0 -20px;padding:10px 20px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.ff_form_main_nav span.plugin-name{font-size:16px;color:#6e6e6e;margin-right:30px}.ff_form_main_nav .ninja-tab{padding:10px;display:inline-block;text-decoration:none;color:#000;font-size:15px;line-height:16px;margin-right:15px;border-bottom:2px solid transparent}.ff_form_main_nav .ninja-tab:focus{-webkit-box-shadow:none;box-shadow:none}.ff_form_main_nav .ninja-tab.ninja-tab-active{font-weight:700;border-bottom:2px solid #ffd65b}.ff_form_main_nav .ninja-tab.buy_pro_tab{background:#e04f5e;padding:10px 25px;color:#fff}.el-dialog__wrapper .el-dialog{background:transparent}.el-dialog__wrapper .el-dialog__header{display:block;overflow:hidden;background:#f5f5f5;border:1px solid #ddd;padding:10px;border-top-left-radius:5px;border-top-right-radius:5px}.el-dialog__wrapper .el-dialog__header .el-dialog__headerbtn{top:12px}.el-dialog__wrapper .el-dialog__footer{background:#f5f5f5;border-bottom-left-radius:5px;border-bottom-right-radius:5px}.el-dialog__wrapper .el-dialog__body{padding:20px;background:#fff;border-radius:0}.el-dialog__wrapper .el-dialog__body .dialog-footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box;background:#f5f5f5;border-bottom-left-radius:5px;border-bottom-right-radius:5px;width:auto;display:block;margin:0 -20px -20px}.ff_nav_top{overflow:hidden;display:block;margin-bottom:15px;width:100%}.ff_nav_top .ff_nav_title{float:left;width:50%}.ff_nav_top .ff_nav_title h3{float:left;margin:0;padding:0;line-height:28px}.ff_nav_top .ff_nav_title .ff_nav_sub_actions{display:inline-block;margin-left:20px}.ff_nav_top .ff_nav_action{float:left;width:50%;text-align:right}.ff_nav_top .ff_search_inline{width:200px;text-align:right;display:inline-block}.el-text-primary{color:#20a0ff}.el-text-info{color:#58b7ff}.el-text-success{color:#13ce66}.el-text-warning{color:#f7ba2a}.el-text-danger{color:#ff4949}.el-notification__content{text-align:left}.el-input-group--append .el-input-group__append{left:-2px}.action-buttons .el-button+.el-button{margin-left:0}.el-box-card{-webkit-box-shadow:none;box-shadow:none}.el-box-footer,.el-box-header{background-color:#edf1f6;padding:15px}.el-box-body{padding:15px}.el-form-item__force-inline.el-form-item__label{float:left;padding:11px 12px 11px 0}.el-form-item .el-form-item{margin-bottom:10px}.el-form-item__content .line{text-align:center}.el-basic-collapse{border:0;margin-bottom:15px}.el-basic-collapse .el-collapse-item__header{padding-left:0;display:inline-block;border:0}.el-basic-collapse .el-collapse-item__wrap{border:0}.el-basic-collapse .el-collapse-item__content{padding:0;background-color:#fff}.el-collapse-settings{margin-bottom:15px}.el-collapse-settings .el-collapse-item__header{background:#f1f1f1;padding-left:20px}.el-collapse-settings .el-collapse-item__content{padding-
|